mirror of
https://github.com/dataelement/bisheng.git
synced 2026-08-28 17:14:44 +08:00
1673849930
- 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
298 lines
19 KiB
Python
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()
|