Files
bisheng/tools/apply_missing_errcode_i18n.py
30388 1673849930 fix(i18n,auth,ux): errcode locale sweep, disabled-login hint, department UI
- Return UserForbiddenError when disabled account matches login id (not 10600).
- Add UserDao.aexists_disabled_login_account; align UserForbiddenError copy.
- Extend axios error mapping and fill bs.json errors (zh/en/ja) plus maint. scripts under tools/.
- Department tree tooltip, resizable pane, settings toasts; login redirect to /admin.
- request.ts: map common English status_message to i18n keys.

Made-with: Cursor
2026-04-21 18:32:54 +08:00

298 lines
19 KiB
Python

"""Merge missing backend errcode keys into platform bs.json (zh-Hans / en-US / ja).
English text comes from tools/errcode_en_from_ast.json (AST extraction).
Chinese / Japanese are curated in ERR_ZH / ERR_JA below.
Run from repo root: python tools/apply_missing_errcode_i18n.py
"""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
AST_PATH = ROOT / "tools" / "errcode_en_from_ast.json"
LOCALES = ROOT / "src/frontend/platform/public/locales"
# Keys here must cover every numeric code present in errcode_en_from_ast.json
# but absent from a locale file's ``errors`` object (see main()).
ERR_ZH: dict[int, str] = {
10990: "后台处理中,请稍后重试。",
10810: "请在工作台配置向量嵌入模型。",
12043: "未找到已使用的应用。",
12044: "已使用的应用未上线。",
14002: "无权限执行该消息操作。",
14003: "消息已审批或已拒绝,无法重复处理。",
18000: "知识空间不存在。",
18001: "最多可创建 30 个知识空间。",
18010: "文件夹不存在。",
18011: "目录层级不能超过 10 级。",
18012: "该目录下已存在同名文件夹。",
18020: "文件不存在。",
18021: "该空间内已存在同名或同内容文件。",
18022: "重命名时不能更改文件扩展名。",
18023: "该空间内已存在同名文件。",
18024: "文件大小超出限制。",
18030: "私有知识空间不可订阅。",
18031: "您已订阅或已申请订阅该知识空间。",
18032: "最多可订阅 50 个知识空间。",
18040: "无权限执行此操作,仅创建者或管理员可操作。",
18050: "该空间内标签已存在。",
19000: "无权限。",
19001: "权限校验失败。",
19002: "授权服务不可用。",
19003: "资源类型或 ID 无效。",
19004: "写入授权关系失败。",
19005: "权限关系无效。",
19010: "频道不存在。",
19011: "无权访问私有频道。",
19012: "已订阅该频道或申请待审核。",
19013: "无权限执行该频道操作。",
19040: "文章不存在。",
19041: "聊天会话不存在。",
19050: "创建频道数量已达上限(最多 10 个)。",
19051: "管理员数量已达上限(最多 5 人)。",
19052: "订阅频道数量已达上限(最多 20 个)。",
19053: "知识空间未配置大语言模型,请先在工作台完成配置。",
19101: "主部门变更被阻止:用户在原租户下仍有资源,请先迁移资源或关闭 user_tenant_sync.enforce_transfer_before_relocate。",
19102: "无法解析叶子租户:无主部门且无可用默认租户。",
19103: "登录状态已失效(token_version 不匹配),请重新登录。",
19104: "解析叶子租户时发现租户环:主部门路径存在自引用挂载点。",
19201: "无法连接 OpenFGA 服务。",
19202: "未找到对应 model_id 的 OpenFGA 授权模型。",
19203: "OpenFGA 关系写入补偿在多次重试后仍失败。",
19204: "禁止授予根租户管理员,请使用 system:global#super_admin。",
19301: "HMAC 签名校验失败或缺失,请求被拒绝。",
19302: "无法挂载部门:父级链路已存在挂载点(仅支持两层挂载)。",
19303: "叶子租户未激活(已禁用/已归档/孤儿),禁止登录。",
19304: "已存在来源冲突的用户,无法用于 SSO 复用。",
19310: "同步时间戳早于上次记录,本次操作已跳过。",
19311: "同一外部用户正在进行其他 SSO 登录,请稍后重试。",
19312: "系统中缺少部门父级链路,请通过网关先推送父部门(/api/v1/departments/sync)。",
19313: "SSO 登录同步需要提供 primary_dept_external_id。",
19314: "同一组织同步配置正在对账中,本次触发已跳过。",
19315: "未知的关联匹配策略,应为 external_id_map 或 path_plus_name。",
19316: "关联冲突候选为空,或选定的新外部 ID 不在已存储候选中。",
19317: "同时间戳冲突已按删除策略解决(已记录审计日志,请联系管理员复核)。",
19318: "预留错误码(对账相关)。",
19401: "租户配额已用尽。",
19402: "角色配额已用尽。",
19403: "存储配额已用尽。",
19501: "仅根租户可将资源共享给子租户。",
19502: "该资源类型不支持共享。",
19503: "跨租户存储回退失败。",
19504: "缺少租户上下文,无法写入衍生数据。",
19601: "仅资源所有者或租户管理员可转移所有权。",
19602: "单次转移超过 500 条,请拆分为更小的请求。",
19603: "接收方叶子租户须位于资源租户可见范围内。",
19604: "不支持的资源转移类型。",
19605: "MySQL/OpenFGA 事务失败,已回滚全部变更。",
19606: "转出用户与接收用户不能相同。",
19701: "仅全局超级管理员可设置管理员租户范围。",
19702: "管理范围目标租户不存在。",
19801: "根租户共享的大模型服务/模型对子租户管理员只读,仅全局超级管理员可修改。",
19802: "目标大模型不在当前可见租户集合内(可能跨租户引用或已删除)。",
19803: "系统级大模型配置(工作台/知识/助手/评测默认值)仅全局超级管理员可修改。",
19804: "大模型服务地址不在 settings.llm.endpoint_whitelist 允许前缀内。",
20000: "租户不存在。",
20001: "租户已禁用。",
20002: "用户不属于该租户。",
20003: "租户编码已存在。",
20004: "缺少租户上下文。",
20005: "租户下仍有活跃用户,无法删除。",
20006: "不能移除租户的最后一名管理员。",
20007: "用户不属于目标租户。",
20008: "租户创建失败。",
20009: "用户暂无可用的租户。",
22000: "未找到组织同步配置。",
22001: "当前为两层租户树限制,不能在子租户下再挂载租户。",
22002: "目标部门已是挂载点或其下级存在挂载。",
22003: "根部门不能作为子租户挂载点。",
22004: "父租户无效:不存在或不是根租户。",
22005: "存在子租户,无法物理删除该租户。",
22006: "迁移子租户资源时发生 tenant_id 冲突。",
22007: "该部门已存在孤儿租户。",
22008: "根租户受系统保护,禁止禁用/归档/删除。",
22009: "写入 audit_log 失败。",
22010: "仅全局超级管理员可执行从根迁移。",
22011: "从根迁移要求资源的 tenant_id 为 1(根租户)。",
23000: "用户组不存在。",
23001: "该租户下用户组名称已存在。",
23002: "默认用户组不可删除或重命名。",
23003: "用户组仍有成员,无法删除。",
23004: "用户已是该组成员。",
23005: "用户不是该组成员。",
23006: "无权限执行该用户组操作。",
23007: "用户组不再单独设置管理员,仅创建者可管理该组。",
24000: "角色不存在。",
24001: "资源配额已用尽。",
24002: "该范围内角色名称已存在。",
24003: "无权限执行该角色操作。",
24004: "内置角色不可删除或修改核心属性。",
24005: "quota_config 或 department_id 配置无效。",
}
ERR_JA: dict[int, str] = {
10990: "バックグラウンドで処理中です。しばらくしてから再試行してください。",
10810: "ワークベンチで埋め込みモデルを設定してください。",
12043: "使用中のアプリが見つかりません。",
12044: "使用中のアプリはオンラインではありません。",
14002: "このメッセージ操作の権限がありません。",
14003: "メッセージは既に承認または却下済みです。",
18000: "ナレッジスペースが存在しません。",
18001: "ナレッジスペースは最大30個まで作成できます。",
18010: "フォルダが存在しません。",
18011: "ディレクトリの深さは10階層を超えられません。",
18012: "このディレクトリに同名のフォルダが既にあります。",
18020: "ファイルが存在しません。",
18021: "このスペースに同名または同一内容のファイルが既にあります。",
18022: "リネーム時に拡張子を変更できません。",
18023: "このスペースに同名のファイルが既にあります。",
18024: "ファイルサイズが上限を超えています。",
18030: "非公開のナレッジスペースは購読できません。",
18031: "既にこのナレッジスペースを購読または申請済みです。",
18032: "購読できるナレッジスペースは最大50個です。",
18040: "権限がありません。作成者または管理者のみ操作できます。",
18050: "このスペースに同じタグが既に存在します。",
19000: "権限がありません。",
19001: "権限チェックに失敗しました。",
19002: "認可サービスが利用できません。",
19003: "リソース種別またはIDが無効です。",
19004: "認可タプルの書き込みに失敗しました。",
19005: "無効な権限リレーションです。",
19010: "チャンネルが見つかりません。",
19011: "非公開チャンネルへのアクセスが拒否されました。",
19012: "既にこのチャンネルを購読済み、または申請が保留中です。",
19013: "このチャンネル操作の権限がありません。",
19040: "記事が見つかりません。",
19041: "チャット会話が見つかりません。",
19050: "作成できるチャンネル数の上限(最大10)に達しました。",
19051: "管理者数の上限(最大5人)に達しました。",
19052: "購読できるチャンネル数の上限(最大20)に達しました。",
19053: "ナレッジスペース用LLMが未設定です。ワークベンチで設定してください。",
19101: "主所属部門の変更がブロックされました。旧テナントにリソースが残っています。先に移譲するか設定を確認してください。",
19102: "リーフテナントを解決できません。主所属部門もデフォルトテナントもありません。",
19103: "ログイン状態が無効です(token_versionの不一致)。再ログインしてください。",
19104: "リーフテナント解決中にテナント循環を検出しました。主所属パスに自己参照マウントがあります。",
19201: "OpenFGAサービスに接続できません。",
19202: "指定model_idのOpenFGA認可モデルが見つかりません。",
19203: "OpenFGAタプル書き込みの補償が最大リトライ後も失敗しました。",
19204: "ルートテナント管理者の付与は禁止されています。system:global#super_adminを使用してください。",
19301: "HMAC署名が不正または欠落しています。",
19302: "部門をマウントできません。親チェーンに既にマウントポイントがあります(2層制限)。",
19303: "リーフテナントが有効ではありません(無効/アーカイブ/孤立)。ログインできません。",
19304: "ソースが競合する既存ユーザーはSSOで再利用できません。",
19310: "受信タイムスタンプが古いため操作をスキップしました。",
19311: "同一外部ユーザーの別SSOログインが進行中です。",
19312: "bishengに部門の親チェーンがありません。先に親部門を同期してください。",
19313: "SSOログイン同期にはprimary_dept_external_idが必要です。",
19314: "同一設定の別リコンサイルが実行中のため、このトリガーをスキップしました。",
19315: "不明なrelink matching_strategyです。external_id_mapまたはpath_plus_nameが必要です。",
19316: "relink競合候補が空か、選択した新外部IDが候補に含まれていません。",
19317: "同一タイムスタンプの競合を削除側で解決しました(監査ログ済み)。管理者に確認してください。",
19318: "予約済みのエラーコード(将来のリコンサイル用)。",
19401: "テナントのクォータを超過しました。",
19402: "ロールのクォータを超過しました。",
19403: "ストレージのクォータを超過しました。",
19501: "ルートテナントのみが子テナントへリソースを共有できます。",
19502: "このリソース種別は共有をサポートしていません。",
19503: "クロステナントのストレージフォールバックに失敗しました。",
19504: "テナントコンテキストがありません。派生データを書き込めません。",
19601: "リソース所有者またはテナント管理者のみが所有権を移転できます。",
19602: "一度に移転できる件数は500件を超えられません。分割してください。",
19603: "受信ユーザーのリーフテナントは、リソーステナントの可視範囲内である必要があります。",
19604: "サポートされていないリソース種別です。",
19605: "MySQL/OpenFGAトランザクションが失敗し、すべてロールバックされました。",
19606: "移転元ユーザーと移転先ユーザーは同じにできません。",
19701: "グローバルスーパー管理者のみが管理者テナントスコープを設定できます。",
19702: "管理スコープの対象テナントが存在しません。",
19801: "ルート共有のLLMサーバ/モデルは子テナント管理者にとって読み取り専用です。変更はグローバルスーパー管理者のみ可能です。",
19802: "対象LLMモデルが現在の可視テナント集合にありません(参照越しまたは削除済み)。",
19803: "システムレベルのLLM設定はグローバルスーパー管理者のみが変更できます。",
19804: "LLMサーバのエンドポイントがsettings.llm.endpoint_whitelistのいずれの接頭辞にも一致しません。",
20000: "テナントが見つかりません。",
20001: "テナントが無効化されています。",
20002: "ユーザーはこのテナントに属していません。",
20003: "テナントコードが既に存在します。",
20004: "テナントコンテキストがありません。",
20005: "アクティブなユーザーがいるためテナントを削除できません。",
20006: "テナントの最後の管理者を外すことはできません。",
20007: "ユーザーは対象テナントに属していません。",
20008: "テナントの作成に失敗しました。",
20009: "ユーザーに利用可能なテナントがありません。",
22000: "組織同期設定が見つかりません。",
22001: "2層ツリーの制限により、子テナントの下にテナントをマウントできません。",
22002: "対象部門は既にマウントポイントであるか、その配下にマウントがあります。",
22003: "ルート部門を子テナントとしてマウントできません。",
22004: "親テナントが無効です。存在しないかルートではありません。",
22005: "子テナントが存在するため、このテナントを物理削除できません。",
22006: "子テナントリソース移行中にtenant_idの競合が発生しました。",
22007: "この部門には既に孤立テナントが存在します。",
22008: "ルートテナントは保護されているため無効化/アーカイブ/削除は禁止されています。",
22009: "audit_logの書き込みに失敗しました。",
22010: "migrate-from-rootはグローバルスーパー管理者のみが実行できます。",
22011: "migrate-from-rootにはresource.tenant_idが1(ルート)である必要があります。",
23000: "ユーザーグループが見つかりません。",
23001: "このテナント内に同名のユーザーグループが既に存在します。",
23002: "デフォルトのユーザーグループは削除または改名できません。",
23003: "メンバーがいるためユーザーグループを削除できません。",
23004: "ユーザーは既にこのグループのメンバーです。",
23005: "ユーザーはこのグループのメンバーではありません。",
23006: "このユーザーグループ操作の権限がありません。",
23007: "ユーザーグループに個別の管理者はありません。作成者のみが管理できます。",
24000: "ロールが見つかりません。",
24001: "リソースのクォータを超過しました。",
24002: "このスコープ内に同名のロールが既に存在します。",
24003: "このロール操作の権限がありません。",
24004: "組み込みロールは削除できず、コア属性も変更できません。",
24005: "quota_configまたはdepartment_idの値が無効です。",
}
def _numeric_error_keys(errors: dict) -> set[int]:
out: set[int] = set()
for k in errors:
if isinstance(k, int):
out.add(k)
elif isinstance(k, str) and k.isdigit():
out.add(int(k))
return out
def main() -> None:
ast_data = json.loads(AST_PATH.read_text(encoding="utf-8"))
for loc, err_zh, err_ja in (
("zh-Hans", ERR_ZH, ERR_JA),
("en-US", {}, {}), # English from AST only
("ja", ERR_ZH, ERR_JA),
):
path = LOCALES / loc / "bs.json"
data = json.loads(path.read_text(encoding="utf-8"))
errors = data.setdefault("errors", {})
have = _numeric_error_keys(errors)
missing = sorted(int(k) for k in ast_data if int(k) not in have)
added = 0
for code in missing:
sk = str(code)
en = ast_data[sk]["en"]
if loc == "en-US":
text = en
elif loc == "zh-Hans":
if code not in err_zh:
raise KeyError(f"Missing ERR_ZH for {code}")
text = err_zh[code]
else:
if code not in err_ja:
raise KeyError(f"Missing ERR_JA for {code}")
text = err_ja[code]
if sk in errors:
continue
errors[sk] = text
added += 1
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"{loc}: added {added} keys ({path})")
if __name__ == "__main__":
main()