🌐 支持i18n (#501)

*  支持 i18n #410

* 🚨 更改noqa方式

* 📝 添加 CONTRIBUTING.md 文件

* 🌐 将 i18n 默认语言设置为 en-US

* 📝 添加英文版 CONTRIBUTING.md
This commit is contained in:
呵呵です
2024-10-26 18:29:51 +08:00
committed by GitHub
parent a0fd9eaed3
commit b2d5a1e729
17 changed files with 374 additions and 15 deletions

67
CONTRIBUTING.en-US.md Normal file
View File

@@ -0,0 +1,67 @@
# How to Contribute?
## Setting Up the Environment
### For Developers with Basic Python Knowledge
First, you need [Python **3.10**](https://www.python.org/) and [Poetry](https://python-poetry.org/).
Then, you need to clone this repository to your local machine using the `git clone` command, and install dependencies using `poetry install --sync`.
### For Developers with Limited Python Knowledge
Here are **my recommended** best practices:
```bash
# Install uv
# Please refer to https://docs.astral.sh/uv/getting-started/installation/
# Set up the basic Python environment
uv python install 3.10
uv tool install poetry
# Clone the repository
git clone https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
cd nonebot-plugin-tetris-stats
# Install dependencies
uv run --no-project --python 3.10 poetry env use python
poetry install --sync
```
## Development
### Code Development
1. For static code analysis, use [ruff](https://docs.astral.sh/ruff/). You can install the corresponding plugin for your IDE or use the command line with `ruff check ./nonebot_plugin_tetris_stats/` to check the code.
2. For code formatting, use [ruff](https://docs.astral.sh/ruff/). You can install the corresponding plugin for your IDE or use the command line with `ruff format ./nonebot_plugin_tetris_stats/` to format the code.
3. For type checking, use both [basedpyright](https://docs.basedpyright.com/latest/) and [mypy](https://www.mypy-lang.org/). You can install the corresponding plugins for your IDE or use the following commands in the terminal to check the code:
```bash
# basedpyright
basedpyright ./nonebot_plugin_tetris_stats/
# mypy
mypy ./nonebot_plugin_tetris_stats/
```
### Internationalization
This project uses [Tarina](https://github.com/ArcletProject/Tarina) for internationalization support.
#### Adding a New Language
1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory.
2. Run `tarina-lang create {language_code}` * Please note that the language code should preferably follow the [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) standard.
3. Edit the generated `./nonebot_plugin_tetris_stats/i18n/{language_code}.json` file.
#### Updating an Existing Language
1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory.
2. Edit the corresponding `./nonebot_plugin_tetris_stats/i18n/{language_code}.json` file.
#### Adding New Entries
1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory.
2. Edit the `.template.json` file.
3. Run `tarina-lang schema && tarina-lang model`.
4. Modify the language files, adding new entries at least to `en-US.json`.

68
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,68 @@
# 我该如何参与开发?
## 配置环境
### 对于有一定 Python 基础的开发者
首先你需要 [Python **3.10**](https://www.python.org/) 以及 [Poetry](https://python-poetry.org/)。
然后你需要使用`git clone`命令将本仓库克隆到本地,然后使用`poetry install --sync`安装依赖。
### 对于基础不是很好的开发者
以下是**我认为的**最佳实践:
```bash
# 安装 uv
# 请参考 https://docs.astral.sh/uv/getting-started/installation/
# 配置基础 Python 环境
uv python install 3.10
uv tool install poetry
# 克隆仓库
git clone https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
cd nonebot-plugin-tetris-stats
# 安装依赖
uv run --no-project --python 3.10 poetry env use python
poetry install --sync
```
## 开发
### 代码开发
1. 代码静态检查使用 [ruff](https://docs.astral.sh/ruff/)你可以为你的ide安装对应插件来使用也可以在命令行使用`ruff check ./nonebot_plugin_tetris_stats/`来检查代码。
2. 代码格式化使用 [ruff](https://docs.astral.sh/ruff/)你可以为你的ide安装对应插件来使用也可以在命令行使用`ruff format ./nonebot_plugin_tetris_stats/`来格式化代码。
3. 类型检查同时使用 [basedpyright](https://docs.basedpyright.com/latest/) 和 [mypy](https://www.mypy-lang.org/)你可以为你的ide安装对应插件来使用。
也可以在命令行使用下面的命令来检查代码:
```bash
# basedpyright
basedpyright ./nonebot_plugin_tetris_stats/
# mypy
mypy ./nonebot_plugin_tetris_stats/
```
### 国际化
本项目使用 [Tarina](https://github.com/ArcletProject/Tarina) 提供国际化支持。
#### 添加新的语言
1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。
2. 运行 `tarina-lang create {语言代码}` * 请注意,语言代码最好符合 [IETF语言标签](https://zh.wikipedia.org/wiki/IETF%E8%AF%AD%E8%A8%80%E6%A0%87%E7%AD%BE) 的规范。
3. 编辑生成的 `./nonebot_plugin_tetris_stats/i18n/{语言代码}.json` 文件。
#### 更新已有语言
1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。
2. 编辑对应的 `./nonebot_plugin_tetris_stats/i18n/{语言代码}.json` 文件。
#### 添加新的条目
1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。
2. 编辑 `.template.json` 文件。
3. 运行 `tarina-lang schema && tarina-lang model`
4. 修改语言文件,至少为`en-US.json`添加新的条目。

View File

@@ -7,6 +7,7 @@ from nonebot.typing import T_Handler
from nonebot_plugin_alconna import AlcMatches, Alconna, At, CommandMeta, on_alconna
from .. import ns
from ..i18n.model import Lang
from ..utils.exception import MessageFormatError, NeedCatchError
command: Alconna = Alconna(
@@ -30,7 +31,7 @@ def add_block_handlers(handler: Callable[[T_Handler], T_Handler]) -> None:
@handler
async def _(bot: Bot, matcher: Matcher, target: At):
if isinstance(target, At) and target.target == bot.self_id:
await matcher.finish('不能查询bot的信息')
await matcher.finish(Lang.interaction.wrong.query_bot())
from . import tetrio, top, tos # noqa: F401, E402

View File

@@ -1 +0,0 @@
CANT_VERIFY_MESSAGE = '* 由于无法验证绑定信息, 不能保证查询到的用户为本人\n'

View File

@@ -14,10 +14,10 @@ from nonebot_plugin_user import get_user
from sqlalchemy import select
from ....db import query_bind_info, trigger
from ....i18n import Lang
from ....utils.exception import FallbackError
from ....utils.typing import Me
from ... import add_block_handlers, alc
from ...constant import CANT_VERIFY_MESSAGE
from .. import command, get_player
from ..api import Player
from ..constant import GAME_TYPE
@@ -113,9 +113,10 @@ async def _( # noqa: PLR0913
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = UniMessage(CANT_VERIFY_MESSAGE)
player = Player(user_id=bind.game_account, trust=True)
await (message + await make_query_result(player, template or 'v1')).finish()
await (
UniMessage.i18n(Lang.interaction.warning.unverified) + await make_query_result(player, template or 'v1')
).finish()
@alc.assign('TETRIO.query')

View File

@@ -13,6 +13,7 @@ from nonebot_plugin_user import get_user
from yarl import URL
from ....db import query_bind_info, trigger
from ....i18n import Lang
from ....utils.exception import RecordNotFoundError
from ....utils.host import HostPage, get_self_netloc
from ....utils.metrics import get_metrics
@@ -22,7 +23,6 @@ from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Tspi
from ....utils.render.schemas.tetrio.record.blitz import Record, Statistic
from ....utils.screenshot import screenshot
from ....utils.typing import Me
from ...constant import CANT_VERIFY_MESSAGE
from .. import alc
from ..api.player import Player
from ..constant import GAME_TYPE
@@ -60,9 +60,10 @@ async def _(
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = UniMessage(CANT_VERIFY_MESSAGE)
player = Player(user_id=bind.game_account, trust=True)
await (message + UniMessage.image(raw=await make_blitz_image(player))).finish()
await (
UniMessage.i18n(Lang.interaction.warning.unverified) + UniMessage.image(raw=await make_blitz_image(player))
).finish()
@alc.assign('TETRIO.record.blitz')

View File

@@ -13,6 +13,7 @@ from nonebot_plugin_user import get_user
from yarl import URL
from ....db import query_bind_info, trigger
from ....i18n import Lang
from ....utils.exception import RecordNotFoundError
from ....utils.host import HostPage, get_self_netloc
from ....utils.metrics import get_metrics
@@ -22,7 +23,6 @@ from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Stat
from ....utils.render.schemas.tetrio.record.sprint import Record
from ....utils.screenshot import screenshot
from ....utils.typing import Me
from ...constant import CANT_VERIFY_MESSAGE
from .. import alc
from ..api.player import Player
from ..constant import GAME_TYPE
@@ -60,9 +60,10 @@ async def _(
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = UniMessage(CANT_VERIFY_MESSAGE)
player = Player(user_id=bind.game_account, trust=True)
await (message + UniMessage.image(raw=await make_sprint_image(player))).finish()
await (
UniMessage.i18n(Lang.interaction.warning.unverified) + UniMessage.image(raw=await make_sprint_image(player))
).finish()
@alc.assign('TETRIO.record.sprint')

View File

@@ -8,6 +8,7 @@ from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[im
from nonebot_plugin_user import get_user
from ...db import query_bind_info, trigger
from ...i18n import Lang
from ...utils.exception import FallbackError
from ...utils.host import HostPage, get_self_netloc
from ...utils.metrics import TetrisMetricsBasicWithLPM, get_metrics
@@ -18,7 +19,6 @@ from ...utils.render.schemas.top_info import Data as InfoData
from ...utils.render.schemas.top_info import Info
from ...utils.screenshot import screenshot
from ...utils.typing import Me
from ..constant import CANT_VERIFY_MESSAGE
from . import alc
from .api import Player
from .api.schemas.user_profile import Data, UserProfile
@@ -44,7 +44,7 @@ async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Even
if bind is None:
await matcher.finish('未查询到绑定信息')
await (
UniMessage(CANT_VERIFY_MESSAGE)
UniMessage.i18n(Lang.interaction.warning.unverified)
+ await make_query_result(await Player(user_name=bind.game_account, trust=True).get_profile())
).finish()

View File

@@ -14,6 +14,7 @@ from nonebot_plugin_user import get_user
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from ...db import query_bind_info, trigger
from ...i18n import Lang
from ...utils.exception import RequestError
from ...utils.host import HostPage, get_self_netloc
from ...utils.image import get_avatar
@@ -24,7 +25,6 @@ from ...utils.render.schemas.base import People, Ranking
from ...utils.render.schemas.tos_info import Info, Multiplayer, Radar
from ...utils.screenshot import screenshot
from ...utils.typing import Me, Number
from ..constant import CANT_VERIFY_MESSAGE
from . import alc
from .api import Player
from .api.schemas.user_info import UserInfoSuccess
@@ -124,7 +124,7 @@ async def _(
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = CANT_VERIFY_MESSAGE
message = UniMessage.i18n(Lang.interaction.warning.unverified)
player = Player(teaid=bind.game_account, trust=True)
user_info, game_data = await gather(player.get_info(), get_game_data(player))
if game_data is not None:

View File

@@ -0,0 +1,5 @@
{
"default": "en-US",
"frozen": [],
"require": []
}

View File

@@ -0,0 +1,72 @@
{
"title": "Lang Schema",
"description": "Schema for lang file",
"type": "object",
"properties": {
"interaction": {
"title": "Interaction",
"description": "Scope 'interaction' of lang item",
"type": "object",
"additionalProperties": false,
"properties": {
"wrong": {
"title": "Wrong",
"description": "Scope 'wrong' of lang item",
"type": "object",
"additionalProperties": false,
"properties": {
"query_bot": {
"title": "query_bot",
"description": "value of lang item type 'query_bot'",
"type": "string"
}
}
},
"warning": {
"title": "Warning",
"description": "Scope 'warning' of lang item",
"type": "object",
"additionalProperties": false,
"properties": {
"unverified": {
"title": "unverified",
"description": "value of lang item type 'unverified'",
"type": "string"
}
}
}
}
},
"error": {
"title": "Error",
"description": "Scope 'error' of lang item",
"type": "object",
"additionalProperties": false,
"properties": {
"MessageFormatError": {
"title": "Messageformaterror",
"description": "Scope 'MessageFormatError' of lang item",
"type": "object",
"additionalProperties": false,
"properties": {
"TETR.IO": {
"title": "TETR.IO",
"description": "value of lang item type 'TETR.IO'",
"type": "string"
},
"TOS": {
"title": "TOS",
"description": "value of lang item type 'TOS'",
"type": "string"
},
"TOP": {
"title": "TOP",
"description": "value of lang item type 'TOP'",
"type": "string"
}
}
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
{
"$schema": ".template.schema.json",
"scopes": [
{
"scope": "interaction",
"types": [
{ "subtype": "wrong", "types": ["query_bot"] },
{ "subtype": "warning", "types": ["unverified"] }
]
},
{
"scope": "error",
"types": [{ "subtype": "MessageFormatError", "types": ["TETR.IO", "TOS", "TOP"] }]
}
]
}

View File

@@ -0,0 +1,54 @@
{
"title": "Template",
"description": "Template for lang items to generate schema for lang files",
"type": "object",
"properties": {
"scopes": {
"title": "Scopes",
"description": "All scopes of lang items",
"type": "array",
"uniqueItems": true,
"items": {
"title": "Scope",
"description": "First level of all lang items",
"type": "object",
"properties": {
"scope": {
"type": "string",
"description": "Scope name"
},
"types": {
"type": "array",
"description": "All types of lang items",
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "string",
"description": "Value of lang item"
},
{
"type": "object",
"properties": {
"subtype": {
"type": "string",
"description": "Subtype name of lang item"
},
"types": {
"type": "array",
"description": "All subtypes of lang items",
"uniqueItems": true,
"items": {
"$ref": "#/properties/scopes/items/properties/types/items"
}
}
}
}
]
}
}
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
# This file is @generated by tarina.lang CLI tool
# It is not intended for manual editing.
# ruff: noqa: E402, F401, PLC0414
from pathlib import Path
from tarina.lang import lang # type: ignore[import-untyped]
lang.load(Path(__file__).parent)
from .model import Lang as Lang

View File

@@ -0,0 +1,16 @@
{
"$schema": ".lang.schema.json",
"interaction": {
"wrong": { "query_bot": "Can't query bot's information" },
"warning": {
"unverified": "* Because I can't verify account linking information, I can't guarantee the info I found is yourself/themself."
}
},
"error": {
"MessageFormatError": {
"TETR.IO": "Username/ID is invalid",
"TOS": "Username/ID is invalid",
"TOP": "Username is invalid"
}
}
}

View File

@@ -0,0 +1,32 @@
# This file is @generated by tarina.lang CLI tool
# It is not intended for manual editing.
from tarina.lang.model import LangItem, LangModel # type: ignore[import-untyped]
class InteractionWrong:
query_bot: LangItem = LangItem('interaction', 'wrong.query_bot')
class InteractionWarning:
unverified: LangItem = LangItem('interaction', 'warning.unverified')
class Interaction:
wrong = InteractionWrong
warning = InteractionWarning
class ErrorMessageformaterror:
TETR_IO: LangItem = LangItem('error', 'MessageFormatError.TETR.IO')
TOS: LangItem = LangItem('error', 'MessageFormatError.TOS')
TOP: LangItem = LangItem('error', 'MessageFormatError.TOP')
class Error:
MessageFormatError = ErrorMessageformaterror
class Lang(LangModel):
interaction = Interaction
error = Error

View File

@@ -0,0 +1,14 @@
{
"$schema": ".lang.schema.json",
"interaction": {
"wrong": { "query_bot": "不能查询bot的信息" },
"warning": { "unverified": "* 由于无法验证绑定信息, 不能保证查询到的用户为本人" }
},
"error": {
"MessageFormatError": {
"TETR.IO": "用户名/ID不合法",
"TOS": "用户名/ID不合法",
"TOP": "用户名不合法"
}
}
}