Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ef97957
Add i18n support and related files
tomo-ris Feb 27, 2026
18df281
feat(i18n): update ftl_translate defaults and sync translation files
tomo-ris Feb 27, 2026
8347fcd
refactor(i18n): update error messages to use translation function
tomo-ris Feb 27, 2026
af24027
feat(i18n): add Japanese translation and fix variable placeholders
tomo-ris Feb 27, 2026
06268dc
refactor(i18n): remove unused English translation file
tomo-ris Feb 27, 2026
240fcae
feat(i18n): add Chinese (Simplified) translation for error messages
tomo-ris Feb 27, 2026
16201b2
Implement feature X to enhance user experience and optimize performance
tomo-ris Feb 27, 2026
c3336d0
refactor(i18n): simplify exception handling and improve code formatting
tomo-ris Feb 27, 2026
ce7c3c6
Refactor code structure for improved readability and maintainability
tomo-ris Feb 28, 2026
c700250
refactor(i18n): improve code formatting and enhance exception handlin…
tomo-ris Feb 28, 2026
8d17871
feat: integrate translation function for logging messages
tomo-ris Feb 28, 2026
dac796e
refactor(i18n): update error messages to use translation function and…
tomo-ris Feb 28, 2026
c26eee5
Merge branch 'master' of https://github.com/AstrBotDevs/AstrBot into …
tomo-ris Feb 28, 2026
7c1dbb9
refactor(i18n): replace hardcoded error messages with translation fun…
tomo-ris Feb 28, 2026
3cf55ee
Merge branch 'AstrBotDevs:master' into feat/i18n
united-pooh Feb 28, 2026
e70abbd
refactor(i18n): update error messages to use translation function and…
tomo-ris Mar 1, 2026
244fab8
Merge remote-tracking branch 'upstream/master' into feat/i18n
tomo-ris Mar 1, 2026
01bddd8
refactor(i18n): update error messages to use translation function in …
tomo-ris Mar 1, 2026
12a7a03
refactor: code structure for improved readability and maintainability
tomo-ris Mar 1, 2026
07ce12f
Merge branch 'master' into feat/i18n
united-pooh Mar 1, 2026
d1788ca
Add pytest_asyncio to dependencies in i18n-check.yml
united-pooh Mar 2, 2026
91e2be6
Merge branch 'master' into feat/i18n
tomo-ris Mar 2, 2026
a7b4581
refactor(i18n): remove unused i18n files and update FTL messages
tomo-ris Mar 2, 2026
265360d
Merge remote-tracking branch 'origin/feat/i18n' into feat/i18n
tomo-ris Mar 2, 2026
74d5cb2
i18n:先给按钮注册i18n
Li-shi-ling Mar 1, 2026
0081b9f
rm:删除原本的i18n切换逻辑
Li-shi-ling Mar 1, 2026
ff8ec32
add:在平台日志页面添加下拉框
Li-shi-ling Mar 1, 2026
78bfac5
add:添加路由注册
Li-shi-ling Mar 1, 2026
3af3cee
fix:修改t的导入
Li-shi-ling Mar 1, 2026
de97421
fix:添加语言修改后的保留功能
Li-shi-ling Mar 1, 2026
09d8b7b
fix:将修改server文件时引入的错误logeer删除,并删除todo
Li-shi-ling Mar 2, 2026
216f5b3
Update pytest command to use python -m
united-pooh Mar 2, 2026
5ee5df8
fix:LangRoute删除不必要的引入,修改错误的日志
Li-shi-ling Mar 2, 2026
4fce6bb
Add PYTHONPATH to I18n usage check workflow
united-pooh Mar 2, 2026
9715f35
Add installation of requirements for I18n check
united-pooh Mar 2, 2026
cd5dd0a
Create test_i18n.py
united-pooh Mar 2, 2026
ebf9669
chore(i18n): fix missing message IDs and parameter mismatches in i18n…
tomo-ris Mar 2, 2026
5e9692a
fix(dashboard): fix port number being formatted with commas in log ou…
tomo-ris Mar 2, 2026
7a9d9e5
chore(i18n): fix number formatting and WebUI address display
tomo-ris Mar 2, 2026
cc048f0
Update astrbot/core/lang.py
united-pooh Mar 2, 2026
da63a6d
Merge branch 'master' into feat/i18n
united-pooh Mar 2, 2026
d7dd9c5
feat(i18n): persist backend locale and restore on startup
letr007 Mar 3, 2026
12d21cb
fix(i18n): validate and normalize persisted locale
letr007 Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/i18n-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: I18n Usage Check
env:
PYTHONPATH: .
on:
pull_request:
paths:
- '**.py'

jobs:
i18n-check:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install fluent.runtime pytest pytest_asyncio

- name: Run I18n Check
run: |
python -m pytest --tb=line tests/test_i18n.py
15 changes: 8 additions & 7 deletions astrbot/builtin_stars/astrbot/long_term_memory.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from astrbot.core.lang import t
import datetime
import random
import uuid
Expand Down Expand Up @@ -28,7 +29,7 @@ def cfg(self, event: AstrMessageEvent):
try:
max_cnt = int(cfg["provider_ltm_settings"]["group_message_max_cnt"])
except BaseException as e:
logger.error(e)
logger.error(t("msg-5bdf8f5c", e=e))
max_cnt = 300
image_caption_prompt = cfg["provider_settings"]["image_caption_prompt"]
image_caption_provider_id = cfg["provider_ltm_settings"].get(
Expand Down Expand Up @@ -74,9 +75,9 @@ async def get_image_caption(
else:
provider = self.context.get_provider_by_id(image_caption_provider_id)
if not provider:
raise Exception(f"没有找到 ID 为 {image_caption_provider_id} 的提供商")
raise Exception(t("msg-8e11fa57", image_caption_provider_id=image_caption_provider_id))
if not isinstance(provider, Provider):
raise Exception(f"提供商类型错误({type(provider)}),无法获取图片描述")
raise Exception(t("msg-8ebaa397", res=type(provider)))
response = await provider.text_chat(
prompt=image_caption_prompt,
session_id=uuid.uuid4().hex,
Expand Down Expand Up @@ -128,22 +129,22 @@ async def handle_message(self, event: AstrMessageEvent) -> None:
try:
url = comp.url if comp.url else comp.file
if not url:
raise Exception("图片 URL 为空")
raise Exception(t("msg-30954f77"))
caption = await self.get_image_caption(
url,
cfg["image_caption_provider_id"],
cfg["image_caption_prompt"],
)
parts.append(f" [Image: {caption}]")
except Exception as e:
logger.error(f"获取图片描述失败: {e}")
logger.error(t("msg-62de0c3e", e=e))
else:
parts.append(" [Image]")
elif isinstance(comp, At):
parts.append(f" [At: {comp.name}]")

final_message = "".join(parts)
logger.debug(f"ltm | {event.unified_msg_origin} | {final_message}")
logger.debug(t("msg-d0647999", res=event.unified_msg_origin, final_message=final_message))
self.session_chats[event.unified_msg_origin].append(final_message)
if len(self.session_chats[event.unified_msg_origin]) > cfg["max_cnt"]:
self.session_chats[event.unified_msg_origin].pop(0)
Expand Down Expand Up @@ -180,7 +181,7 @@ async def after_req_llm(
if llm_resp.completion_text:
final_message = f"[You/{datetime.datetime.now().strftime('%H:%M:%S')}]: {llm_resp.completion_text}"
logger.debug(
f"Recorded AI response: {event.unified_msg_origin} | {final_message}"
t("msg-133c1f1d", res=event.unified_msg_origin, final_message=final_message)
)
self.session_chats[event.unified_msg_origin].append(final_message)
cfg = self.cfg(event)
Expand Down
21 changes: 11 additions & 10 deletions astrbot/builtin_stars/astrbot/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from astrbot.core.lang import t
import traceback

from astrbot.api import star
Expand All @@ -16,7 +17,7 @@ def __init__(self, context: star.Context) -> None:
try:
self.ltm = LongTermMemory(self.context.astrbot_config_mgr, self.context)
except BaseException as e:
logger.error(f"聊天增强 err: {e}")
logger.error(t("msg-3df554a1", e=e))

def ltm_enabled(self, event: AstrMessageEvent):
ltmse = self.context.get_config(umo=event.unified_msg_origin)[
Expand Down Expand Up @@ -44,13 +45,13 @@ async def on_message(self, event: AstrMessageEvent):
try:
await self.ltm.handle_message(event)
except BaseException as e:
logger.error(e)
logger.error(t("msg-5bdf8f5c", e=e))

if need_active:
"""主动回复"""
provider = self.context.get_using_provider(event.unified_msg_origin)
if not provider:
logger.error("未找到任何 LLM 提供商。请先配置。无法主动回复")
logger.error(t("msg-bb6ff036"))
return
try:
conv = None
Expand All @@ -60,7 +61,7 @@ async def on_message(self, event: AstrMessageEvent):

if not session_curr_cid:
logger.error(
"当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /switch 序号 切换或者 /new 创建一个会话。",
t("msg-afa050be"),
)
return

Expand All @@ -72,7 +73,7 @@ async def on_message(self, event: AstrMessageEvent):
prompt = event.message_str

if not conv:
logger.error("未找到对话,无法主动回复")
logger.error(t("msg-9a6a6b2e"))
return

yield event.request_llm(
Expand All @@ -81,8 +82,8 @@ async def on_message(self, event: AstrMessageEvent):
conversation=conv,
)
except BaseException as e:
logger.error(traceback.format_exc())
logger.error(f"主动回复失败: {e}")
logger.error(t("msg-78b9c276", res=traceback.format_exc()))
logger.error(t("msg-b177e640", e=e))

@filter.on_llm_request()
async def decorate_llm_req(
Expand All @@ -93,7 +94,7 @@ async def decorate_llm_req(
try:
await self.ltm.on_req_llm(event, req)
except BaseException as e:
logger.error(f"ltm: {e}")
logger.error(t("msg-24d2f380", e=e))

@filter.on_llm_response()
async def record_llm_resp_to_ltm(
Expand All @@ -104,7 +105,7 @@ async def record_llm_resp_to_ltm(
try:
await self.ltm.after_req_llm(event, resp)
except Exception as e:
logger.error(f"ltm: {e}")
logger.error(t("msg-24d2f380", e=e))

@filter.after_message_sent()
async def after_message_sent(self, event: AstrMessageEvent) -> None:
Expand All @@ -115,4 +116,4 @@ async def after_message_sent(self, event: AstrMessageEvent) -> None:
if clean_session:
await self.ltm.remove_session(event)
except Exception as e:
logger.error(f"ltm: {e}")
logger.error(t("msg-24d2f380", e=e))
25 changes: 13 additions & 12 deletions astrbot/builtin_stars/builtin_commands/commands/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from astrbot.core.lang import t
from astrbot.api import star
from astrbot.api.event import AstrMessageEvent, MessageChain, MessageEventResult
from astrbot.core.config.default import VERSION
Expand All @@ -13,65 +14,65 @@ async def op(self, event: AstrMessageEvent, admin_id: str = "") -> None:
if not admin_id:
event.set_result(
MessageEventResult().message(
"使用方法: /op <id> 授权管理员;/deop <id> 取消管理员。可通过 /sid 获取 ID。",
t("msg-ad019976"),
),
)
return
self.context.get_config()["admins_id"].append(str(admin_id))
self.context.get_config().save_config()
event.set_result(MessageEventResult().message("授权成功。"))
event.set_result(MessageEventResult().message(t("msg-1235330f")))

async def deop(self, event: AstrMessageEvent, admin_id: str = "") -> None:
"""取消授权管理员。deop <admin_id>"""
if not admin_id:
event.set_result(
MessageEventResult().message(
"使用方法: /deop <id> 取消管理员。可通过 /sid 获取 ID。",
t("msg-e78847e0"),
),
)
return
try:
self.context.get_config()["admins_id"].remove(str(admin_id))
self.context.get_config().save_config()
event.set_result(MessageEventResult().message("取消授权成功。"))
event.set_result(MessageEventResult().message(t("msg-012152c1")))
except ValueError:
event.set_result(
MessageEventResult().message("此用户 ID 不在管理员名单内。"),
MessageEventResult().message(t("msg-5e076026")),
)

async def wl(self, event: AstrMessageEvent, sid: str = "") -> None:
"""添加白名单。wl <sid>"""
if not sid:
event.set_result(
MessageEventResult().message(
"使用方法: /wl <id> 添加白名单;/dwl <id> 删除白名单。可通过 /sid 获取 ID。",
t("msg-7f8eedde"),
),
)
return
cfg = self.context.get_config(umo=event.unified_msg_origin)
cfg["platform_settings"]["id_whitelist"].append(str(sid))
cfg.save_config()
event.set_result(MessageEventResult().message("添加白名单成功。"))
event.set_result(MessageEventResult().message(t("msg-de1b0a87")))

async def dwl(self, event: AstrMessageEvent, sid: str = "") -> None:
"""删除白名单。dwl <sid>"""
if not sid:
event.set_result(
MessageEventResult().message(
"使用方法: /dwl <id> 删除白名单。可通过 /sid 获取 ID。",
t("msg-59d6fcbe"),
),
)
return
try:
cfg = self.context.get_config(umo=event.unified_msg_origin)
cfg["platform_settings"]["id_whitelist"].remove(str(sid))
cfg.save_config()
event.set_result(MessageEventResult().message("删除白名单成功。"))
event.set_result(MessageEventResult().message(t("msg-4638580f")))
except ValueError:
event.set_result(MessageEventResult().message("此 SID 不在白名单内。"))
event.set_result(MessageEventResult().message(t("msg-278fb868")))

async def update_dashboard(self, event: AstrMessageEvent) -> None:
"""更新管理面板"""
await event.send(MessageChain().message("正在尝试更新管理面板..."))
await event.send(MessageChain().message(t("msg-1dee5007")))
await download_dashboard(version=f"v{VERSION}", latest=False)
await event.send(MessageChain().message("管理面板更新完成。"))
await event.send(MessageChain().message(t("msg-76bea66c")))
23 changes: 10 additions & 13 deletions astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from astrbot.core.lang import t
from astrbot.api import star
from astrbot.api.event import AstrMessageEvent, MessageChain
from astrbot.core.star.filter.command import CommandFilter
Expand Down Expand Up @@ -31,11 +32,7 @@ async def alter_cmd(self, event: AstrMessageEvent) -> None:
if token.len < 3:
await event.send(
MessageChain().message(
"该指令用于设置指令或指令组的权限。\n"
"格式: /alter_cmd <cmd_name> <admin/member>\n"
"例1: /alter_cmd c1 admin 将 c1 设为管理员指令\n"
"例2: /alter_cmd g1 c1 admin 将 g1 指令组的 c1 子指令设为管理员指令\n"
"/alter_cmd reset config 打开 reset 权限配置",
t("msg-d7a36c19"),
),
)
return
Expand Down Expand Up @@ -63,26 +60,26 @@ async def alter_cmd(self, event: AstrMessageEvent) -> None:
修改指令格式:
/alter_cmd reset scene <场景编号> <admin/member>
例如: /alter_cmd reset scene 2 member"""
await event.send(MessageChain().message(config_menu))
await event.send(MessageChain().message(t("msg-afe0fa58", config_menu=config_menu)))
return

if cmd_name == "reset" and cmd_type == "scene" and token.len >= 4:
scene_num = token.get(3)
perm_type = token.get(4)

if scene_num is None or perm_type is None:
await event.send(MessageChain().message("场景编号和权限类型不能为空"))
await event.send(MessageChain().message(t("msg-0c85d498")))
return

if not scene_num.isdigit() or int(scene_num) < 1 or int(scene_num) > 3:
await event.send(
MessageChain().message("场景编号必须是 1-3 之间的数字"),
MessageChain().message(t("msg-4e0afcd1")),
)
return

if perm_type not in ["admin", "member"]:
await event.send(
MessageChain().message("权限类型错误,只能是 admin 或 member"),
MessageChain().message(t("msg-830d6eb8")),
)
return

Expand All @@ -94,14 +91,14 @@ async def alter_cmd(self, event: AstrMessageEvent) -> None:

await event.send(
MessageChain().message(
f"已将 reset 命令在{scene.name}场景下的权限设为{perm_type}",
t("msg-d1180ead", res=scene.name, perm_type=perm_type),
),
)
return

if cmd_type not in ["admin", "member"]:
await event.send(
MessageChain().message("指令类型错误,可选类型有 admin, member"),
MessageChain().message(t("msg-8d9bc364")),
)
return

Expand All @@ -124,7 +121,7 @@ async def alter_cmd(self, event: AstrMessageEvent) -> None:
break

if not found_command:
await event.send(MessageChain().message("未找到该指令"))
await event.send(MessageChain().message(t("msg-1f2f65e0")))
return

found_plugin = star_map[found_command.handler_module_path]
Expand Down Expand Up @@ -168,6 +165,6 @@ async def alter_cmd(self, event: AstrMessageEvent) -> None:
cmd_group_str = "指令组" if cmd_group else "指令"
await event.send(
MessageChain().message(
f"已将「{cmd_name}」{cmd_group_str} 的权限级别调整为 {cmd_type}。",
t("msg-cd271581", cmd_name=cmd_name, cmd_group_str=cmd_group_str, cmd_type=cmd_type),
),
)
Loading