Files
huly-platform/plugins/setting-assets/lang/zh.json
T
Don KendallandGitHub 5a3d673e84 feat(account): revocable API tokens managed from account settings (#10624)
* feat: API token management in workspace settings

Add UI and backend support for creating, listing, and revoking
API tokens scoped to workspaces. Includes owner-level workspace
token visibility, OpenAPI documentation, Mongo/Postgres persistence,
and i18n translations.

Signed-off-by: Don Kendall <kendall@donkendall.com>

* feat: enforce API token revocation at transactor level

Embed apiTokenId in JWT extra field and add a per-token revocation
cache (60s TTL) in the transactor REST handler. Revoked tokens are
now rejected within ~60 seconds instead of remaining valid until
JWT expiry.

Adds checkApiTokenRevoked account service method for the transactor
to query individual token revocation status.

Signed-off-by: Don Kendall <kendall@donkendall.com>

* feat: implement Phase 1 API token scopes (read/write/delete)

Add coarse-grained scope enforcement for API tokens. Tokens can now
be created with scopes ['read:*'], ['read:*','write:*'], or
['read:*','write:*','delete:*']. Existing tokens without scopes
retain full access (backward compatible).

- DB: v26 migration adds scopes TEXT[] column to api_tokens
- Types: add scopes field to ApiToken and ApiTokenInfo
- Operations: createApiToken accepts/validates/persists scopes,
  embeds in JWT via extra.scopes
- Enforcement: withSession checks scopes against method; tx handler
  additionally requires delete:* for TxRemoveDoc
- Client: createApiToken signature accepts optional scopes param
- UI: scope preset dropdown in create popup (default: Read Only),
  permissions column in token list with i18n labels
- Also fixes 3 pre-existing TS2322/TS2345 errors in operations.ts

Signed-off-by: Don Kendall <kendall@donkendall.com>

* test: add unit tests for API token scope enforcement

- scopes.test.ts: 8 tests for hasScope() and getRequiredScope() logic
- apiTokenScopes.test.ts: 7 tests for createApiToken scope validation
  (valid scopes, multiple scopes, no scopes backward compat, invalid
  format rejection, empty array rejection, domain-scope rejection)
  and listApiTokens scopes inclusion
- Export hasScope/getRequiredScope from rpc.ts for testability

Signed-off-by: Don Kendall <kendall@donkendall.com>

* fix: address review feedback — role restriction, locale parity, formatting

- Restrict API token creation/revocation to AccountRole.User or higher
  (guests cannot use API tokens), per reviewer suggestion
- Add 5 missing translation keys (ApiTokenPermissions, ApiTokenScopePreset,
  ApiTokenScopeReadOnly, ApiTokenScopeReadWrite, ApiTokenScopeFullAccess)
  to all non-en locale files to fix locale parity CI test
- Fix prettier formatting in apiTokenScopes.test.ts
- Rename local `extra` to `tokenExtra` in createApiToken to avoid
  shadowing the decoded token's `extra` field

Signed-off-by: Don Kendall <kendall@donkendall.com>

* fix: address aonnikov architectural review feedback

- rpc.ts: use system service token for checkApiTokenRevoked so the
  revocation check is not coupled to the user's potentially-revoked
  bearer token; systemAccountUuid + service:'server' ensures account
  service always accepts the call
- ApiDocsSection.svelte: derive transactor base URL from
  login.metadata.LoginEndpoint (set on auth) instead of
  window.location.origin, which is not necessarily the transactor host
- ApiTokenCreatePopup.svelte: replace manual translate() calls and
  themeStore language watch with DropdownLabelsIntl + DropdownIntlItem[],
  which handle i18n automatically; error state is now IntlString
- General.svelte: remove legacy GenerateApiToken button, handler, and
  ApiTokenPopup import in favour of the new ApiTokens settings panel

Signed-off-by: Don Kendall <kendall@donkendall.com>

* fix: formatting in ApiTokenPopup, apiTokenScopes test, and operations

Signed-off-by: Don Kendall <kendall@donkendall.com>

* fix: apply rushx fmt to pass CI formatting check

Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* fix: restore (s as any) cast in server_http.ts removed by ESLint autofix

Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* refactor(token): centralize API token revocation/expiry in verifyToken

Address @aonnikov's review: the bespoke checkApiTokenRevoked RPC and the
ad-hoc revocation cache in the transactor are replaced by a reusable
verifyToken in server-token that checks signature, expiry, and (for
revokable API tokens) revocation via a pluggable checker.

- server-token: add verifyToken + isTokenExpired + setApiTokenRevocationChecker
  (the 'method to verify' metadata the plugin needs, without depending on the
  account client). Revocation cache (60s TTL) now lives here, reusable by any
  service (transactor, blob access, etc.).
- account: the account is now authoritative — wrap() rejects revoked/expired
  API tokens, so any account method (selectWorkspace/getWorkspaceInfo/...)
  naturally 401s. Removed the redundant checkApiTokenRevoked method.
- account-client: drop checkApiTokenRevoked.
- transactor: withSession uses verifyToken; the registered checker reuses the
  existing getLoginInfoByToken instead of a dedicated boolean RPC. Scopes are
  parsed once and threaded through (no re-decode in the tx handler).
- tests: verifyToken/isTokenExpired unit coverage.

Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* fix(setting): derive REST API base from account-provided transactor endpoint

Address @aonnikov: the REST API host must come from the transactor endpoint
returned by the account service (the login endpoint, a ws(s):// URL), not be
constructed from window.location. Convert ws->http and append /api/v1, matching
the existing ServerManagerGeneral pattern.

Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* i18n(setting): translate API token strings across all locales

Address @ArtyomSavchenko: the new API token UI strings were left in English
in non-en locales. Provide translations for ru/de/es/fr/it/pt/pt-br/zh/ja/cs/tr.
The en/ru locale-parity test passes (the prior failure was an en/ru key
mismatch, resolved by the develop merge).

Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* style: wrap long lines to satisfy prettier (account utils import, ApiDocsSection)

Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* fix(api-token): drop unenforceable scopes, harden the token lifecycle

The scopes were only ever consulted in the transactor's REST handler, so
they promised a boundary the platform did not keep:

- the WebSocket transport decodes the token and never looks at scopes, so
  a read-only token could open a socket and issue any transaction;
- even on the REST path, delete:* only matched a top-level TxRemoveDoc and
  was bypassed by nesting the removal in a TxApplyIf;
- nothing stopped a scoped token from calling createApiToken and minting
  an unscoped one.

Narrowing a token's rights has to happen in the pipeline, where it covers
every transport, and that is a larger design than this feature. Until then
a token carries its account's rights and says so, rather than displaying a
restriction that does not hold. Scopes are removed end to end.

What is kept is made to work:

- API tokens can no longer create, list or revoke API tokens. Otherwise a
  leaked token renews itself and revokes the tokens meant to stop it.
- Revocation fails closed. The account is the only authority on it, so an
  unreachable account now rejects the token instead of admitting a revoked
  one to whoever can keep the account busy. Verdicts stay trusted for the
  cache TTL so brief outages do not cut off healthy tokens.
- The revocation cache is bounded and re-checks negative verdicts.
- Revoking your own token no longer requires a role in its workspace, so
  leaving a workspace cannot strand a credential you can never revoke.
- The per-account limit counts only usable tokens; revoked and expired
  ones are kept for the audit trail and used to lock out anyone rotating.
- Rejected REST tokens are logged with a reason, since expired, revoked
  and unverifiable are one opaque 401 from outside.

The unused listWorkspaceApiTokens/revokeWorkspaceApiToken pair is removed;
it had no caller and no test.

Tests cover the role restriction, the API-token guard, ownership on
revoke, the limit accounting and fail-closed revocation. Removing any of
those checks fails them.

Claude-Session: https://claude.ai/code/session_01ANdoXbdn5k2hZy734EwKe7
Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* fix(setting): correct the API token settings page

- Moves the page from workspace settings to account settings. Tokens
  belong to the account: the page already lists them across every
  workspace, and creating one only needs the User role the account
  service checks, not Owner as the category required.
- Surfaces failures. A failed revoke was logged to the console and the
  row re-rendered unchanged; loading workspaces could reject unhandled
  and leave an empty dropdown with no explanation.
- Outside a secure context there is no clipboard API, so the OK button
  did nothing and the dialog had no way out but Cancel. It now closes,
  leaving the token selectable.
- Replaces hardcoded English labels, adds aria-expanded on the docs
  disclosure and labels on the copy targets.
- Fills in the Korean and Polish translations, which were the only two
  locales missing these keys, and drops the API access strings orphaned
  when this PR replaced the old Generate API token button.

Claude-Session: https://claude.ai/code/session_01ANdoXbdn5k2hZy734EwKe7
Signed-off-by: Don Kendall <dkendall@ledoweb.com>

* chore: drop docs/openapi.yaml from the API token PR

Unrelated to this feature and wrong for this repo: it documents a
/_tokens minting service and a tools/mint-token CLI that do not exist
here, uses reverse-proxy prefixes from a self-hosted deployment rather
than the transactor's /api/v1 routes, and states that revocation is a
future enhancement, which is what this PR implements. Nothing references
it. REST API docs belong in their own change, generated against the
routes that exist.

Claude-Session: https://claude.ai/code/session_01ANdoXbdn5k2hZy734EwKe7
Signed-off-by: Don Kendall <dkendall@ledoweb.com>

---------

Signed-off-by: Don Kendall <kendall@donkendall.com>
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
2026-08-04 13:10:48 +07:00

287 lines
14 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"string": {
"Setting": "设置",
"Spaces": "空间",
"Integrations": "集成",
"Support": "支持",
"Privacy": "隐私",
"Terms": "条款",
"AccountSettings": "账户设置",
"Categories": "类别",
"Delete": "删除",
"ChangePassword": "更改密码",
"Disconnect": "断开连接",
"DisconnectAll": "断开所有连接",
"Saving": "保存中...",
"Saved": "已保存",
"Add": "添加",
"AddNew": "添加 {type}",
"Proceed": "继续",
"NewEmail": "新邮箱",
"SendConfirmation": "发送确认码",
"CodeSent": "验证码已发送。请在下方字段中输入。",
"SendAgain": "重新发送",
"SendAgainIn": "可在 后再次发送",
"Value": "值",
"Signout": "退出登录",
"Settings": "设置",
"SelectWorkspace": "选择工作区",
"InviteWorkspace": "邀请到工作区",
"DeleteStatus": "删除状态",
"DeleteStatusConfirm": "您确定要删除此状态吗?",
"Reconnect": "重新连接",
"IntegrationDisabled": "已被禁用",
"IntegrationDisabledSetting": "集成已被禁用",
"IntegrationDisabledDescr": "集成已禁用",
"IntegrationWith": "集成于",
"ClassSetting": "类别设置",
"ClassSettingHint": "一组或一类具有某些共同属性或特征的事物。",
"ClassProperties": "类别属性",
"Classes": "类别",
"Attributes": "属性",
"DeleteAttribute": "删除属性",
"DeleteAttributeConfirm": "您确定要删除此属性吗?",
"DeleteAttributeExistConfirm": "您确定要删除此属性吗?数据将会丢失",
"DeleteMixin": "删除 Mixin",
"DeleteMixinConfirm": "您确定要删除此 Mixin 吗?",
"DeleteMixinExistConfirm": "您确定要删除此 Mixin 吗?数据将不可用",
"Attribute": "属性",
"Custom": "自定义",
"Type": "类型",
"WithTime": "有时间",
"DateMode": "日期模式",
"CreatingAttribute": "创建属性",
"EditAttribute": "编辑属性",
"CreateEnum": "创建枚举",
"EditEnum": "编辑枚举",
"Enums": "枚举",
"EnumsSettingHint": "一组或一类具有某些共同属性或特征的事物。",
"EnumTitle": "枚举标题",
"EnumsCount": "{count, plural, =1 {# 个选项} other {# 个选项}}",
"ProjectTypesCount": "{count, plural, =0 {无项目类型} =1 {# 个项目类型} other {# 个项目类型}}",
"Options": "选项",
"EnterOptionTitle": "输入选项标题",
"NewEnumDialogClose": "您要关闭此对话框吗?",
"NewEnumDialogCloseNote": "所有更改将会丢失",
"NewValue": "新值",
"Leave": "离开工作区",
"LeaveDescr": "您确定要离开工作区吗?此操作无法撤销。",
"Members": "成员",
"WorkspaceSettings": "工作区设置",
"Select": "选择",
"AddOwner": "添加所有者",
"ReadonlyGuest": "只读",
"Guest": "访客",
"User": "用户",
"Maintainer": "维护者",
"Owner": "所有者",
"OwnerFirstName": "所有者的名字",
"OwnerLastName": "所有者的姓氏",
"Role": "角色",
"FailedToSave": "更新密码失败",
"ImportEnum": "导入枚举值",
"ImportEnumCopy": "从剪贴板复制枚举值",
"CreateMixin": "创建 Mixin",
"OldNames": "旧值",
"NewClassName": "输入新的类别名称或从以前的值中选择...",
"ShowAttribute": "显示属性",
"HideAttribute": "隐藏属性",
"Visibility": "可见性",
"Hidden": "隐藏",
"Configure": "配置",
"InviteSettings": "邀请设置",
"RoleCapabilitySettings": "角色权限",
"DefaultInviteRoleForJoin": "通过邀请链接加入后分配的默认角色:",
"InviteLinkGeneratorRoles": "选择可以生成邀请链接的用户角色:",
"DefaultValue": "默认值",
"SelectAValue": "选择一个值",
"DateOnly": "仅日期",
"OnlyTime": "仅时间",
"DateAndTime": "日期和时间",
"Configuration": "配置",
"ConfigurationEnabled": "已启用",
"ConfigurationDisabled": "已禁用",
"ConfigDisable": "禁用",
"ConfigEnable": "启用",
"ConfigBeta": "测试版",
"Properties": "属性",
"TaskTypes": "任务类型",
"Automations": "自动化",
"Collections": "集合",
"ClassColon": "类别:",
"SpaceTypes": "空间类型",
"NewSpaceType": "新空间类型",
"SpaceTypeTitle": "空间类型标题",
"General": "常规",
"Description": "描述",
"CountSpaces": "{count, plural, =0 {无空间} =1 {# 个空间} other {# 个空间}}",
"Roles": "角色",
"RoleName": "角色名称",
"Permissions": "权限",
"Assignees": "受托人",
"DeleteRole": "删除角色",
"DeleteRoleConfirmation": "您确定要删除此角色吗?拥有此角色的所有用户将失去其权限。",
"DeleteWorkspace": "删除工作区",
"DeleteWorkspaceConfirm": "您确定要删除此工作区吗?您和所有其他成员将失去对此工作区的访问权限。工作区中的所有信息将丢失。此操作无法撤销。您要继续吗?",
"DeleteSpaceType": "删除空间类型",
"DeleteSpaceTypeConfirm": "您确定要删除此空间类型吗?",
"WorkspaceName": "工作区名称",
"Workspace": "工作区",
"OwnerOrMaintainerRequired": "您需要成为工作空间所有者或维护者",
"LastOwnerLeaveTitle": "无法离开工作区",
"LastOwnerLeaveMessage": "您是此工作区的唯一所有者。要离开,请先将所有者权限授予其他成员。如果不再有人需要此工作区,请考虑删除该工作区。",
"Backup": "备份",
"BackupLast": "上次备份",
"BackupTotalSnapshots": "快照总数",
"BackupTotalFiles": "文件数量",
"BackupSize": "备份大小",
"BackupLinkInfo": "可以使用 wget 或 curl 等工具递归下载的备份目录的 URL。",
"BackupBearerTokenInfo": "访问备份需要一个持票者令牌。",
"BackupSnapshots": "备份快照",
"BackupFileDownload": "下载文件",
"BackupFiles": "备份文件",
"BackupNoBackup": "当前没有可用的备份。",
"BackupDownloadAll": "下载完整备份",
"BackupPreparingDownload": "正在准备备份…",
"BackupDownloadAllInfo": "将所有备份文件下载为一个 .zip 压缩包,可保存到您的计算机上。",
"BackupCopyScript": "复制下载脚本",
"BackupCopyToken": "复制令牌",
"BackupScriptInfo": "一个使用 curl 下载所有备份文件的 shell 脚本。保存并在终端中运行;它会提示输入您的备份令牌,因此脚本中不会存储任何机密信息。",
"BackupRestoreGuide": "备份与恢复指南",
"BackupRestoreGuideInfo": "关于下载此备份并将其恢复到另一个 Huly 实例的分步说明。",
"NonBackupedBlobs": "未备份的 Blob",
"Calendar": "日历",
"StartOfTheWeek": "本周开始",
"SystemSetupString": "系统设置({day})",
"DefaultString": "违约({day})",
"AddAttribute": "添加属性",
"WorkspaceNamePattern": "名称必须为 40 个字符或更少,不能为空,不能包含特殊字符(<、>、/",
"Mailboxes": "邮箱",
"CreateMailbox": "创建邮箱",
"CreateMailboxPlaceholder": "my-cool-name",
"MailboxNoDomains": "未配置电子邮件域",
"MailboxLimitReached": "已达到邮箱限制",
"MailboxErrorInvalidName": "邮箱名称无效",
"MailboxErrorDomainNotFound": "未找到域",
"MailboxErrorNameRulesViolated": "邮箱名称必须为 {minLen} 到 {maxLen} 个字符长",
"MailboxErrorMailboxExists": "此邮箱名称已被占用",
"MailboxErrorMailboxCountLimit": "您的帐户已达到邮箱数量限制",
"DeleteMailbox": "删除邮箱",
"MailboxDeleteConfirmation": "您确定要删除此邮箱吗?",
"DisablePermissions": "禁用基于角色的访问控制",
"EnablePermissions": "启用基于角色的访问控制",
"DisablePermissionsConfirmation": "您确定要禁用基于角色的访问控制吗?所有角色和权限都将被禁用。",
"EnablePermissionsConfirmation": "您确定要启用基于角色的访问控制吗?所有角色和权限都将被启用。",
"BetaWarning": "标记为测试版的模块可用于实验目的,可能无法完全正常工作。我们不建议在此时依赖测试版功能进行关键工作。",
"IntegrationFailed": "创建集成失败",
"IntegrationError": "请重试,如果问题仍然存在,请联系客服支持",
"EmailIsUsed": "该电子邮件地址已被其他账户使用",
"Customize": "自定义",
"GuestAccess": "匿名访客",
"GuestAccessDescription": "允许匿名用户以只读模式访问工作区",
"GuestSignUpDescription": "允许匿名用户以访客身份加入您的工作区,获得有限的编辑权限",
"GuestChannelsDescription": "访客加入后可以发送消息的频道",
"GuestChannelsArrayLabel": "选择频道",
"GuestSelectSpaces": "选择空间",
"GuestAutoJoinAvailableSpaces": "自动加入空间",
"GuestAutoJoinAvailableSpacesHint": "每个应用卡片都有各自的「自动加入空间」行:选择在访客激活时要添加到的位置;更改会立即生效。",
"GuestAnonymousVisibleSpaces": "匿名访客可见的空间",
"GuestAnonymousVisibleSpacesHint": "每个应用卡片都有各自的行:选择在哪些空间将只读匿名账号添加为成员,以便未登录访客可以打开这些空间;更改会立即生效。",
"ManageIdentities": "管理身份",
"Release": "释放",
"ReleaseSocialId": "释放社交ID",
"ReleaseSocialIdConfirm": "您确定要释放此社交ID: {socialId} 吗?这将从您的账户中删除它,您将不再能够使用它登录。此外,所有相关的集成也将被删除。",
"ReleasePrimarySocialId": "释放主要社交ID",
"ReleasePrimarySocialIdConfirm": "释放您当前的主要社交ID需要重新加载页面。您确定要继续吗?",
"Login": "登录",
"Primary": "主要",
"MyIntegrations": "我的集成",
"AllIntegrations": "所有集成",
"ConnectedIntegrations": "已连接的集成",
"AvailableIntegrations": "可用的集成 ",
"Connect": "连接",
"Integrate": "集成",
"FailedToLoadIntegrations": "加载集成失败",
"FailedToDisconnect": "断开集成失败",
"ServiceIsUnavailable": "服务不可用",
"Integrated": "已集成",
"Connected": "已连接",
"Disconnected": "已断开",
"Available": "可用",
"NotConnectedIntegration": "帐户 {account} 未与工作区集成",
"IntegrationIsUnstable": "集成服务出现问题。某些功能可能无法正常工作。",
"MinValue": "最小值",
"Restricted": "受限",
"RestrictedAttributeWarning": "您确定要限制对此属性的更改吗?此操作将为该属性创建权限,且无法撤销。",
"MaxValue": "最大值",
"IntegerOnly": "仅整数",
"AccessControl": "访问控制",
"DangerZone": "危险区域",
"IdentifierExists": "标识符已存在",
"PasswordAgingRule": "密码老化规则",
"PasswordAgingRuleDescription": "用户需要更改密码的天数",
"OfficeSettings": "办公室设置",
"OfficeDefaultSettings": "会议室的默认设置",
"DefaultStartWithTranscription": "在新办公室启用转录",
"DefaultStartWithRecording": "在新办公室启用录制",
"GuestPermissionsSettings": "访客",
"GuestPermissionsApplicationPermissions": "应用权限",
"GuestPermissionsApplicationPermissionsHint": "选择访客可以使用哪些应用,然后在下方调整每个应用的权限。",
"GuestPermissionsTabGuest": "访客",
"GuestPermissionsTabAnonymousGuest": "匿名访客",
"GuestPermissionsAnonymousApplicationHint": "匿名(只读)访客的应用访问。他们可见的应用也可能取决于部署配置。",
"ImportDocumentPermission": "导入文档",
"ImportDocumentDescription": "授予用户将文档导入工作区的权限",
"SelectUsers": "选择用户",
"ShowInTitle": "在标题中显示",
"SpaceMembersOnly": "仅限空间成员",
"Reset": "重置",
"Security": "安全",
"TwoFactorAuth": "双因素认证",
"TwoFactorAuthDescription": "双因素认证为您的帐户增加额外的安全层",
"EnableTwoFactorAuth": "启用双因素认证",
"DisableTwoFactorAuth": "禁用双因素认证",
"TwoFactorAuthEnabled": "双因素认证已启用",
"TwoFactorAuthDisabled": "双因素认证已禁用",
"ShowQRCode": "显示QR码",
"EnterVerificationCode": "输入验证码",
"OverrideAttribute": "覆盖属性",
"Required": "必须",
"ApiBaseUrl": "基础 URL",
"ApiEndpointAccount": "获取账户信息",
"ApiEndpointFindAll": "按类查询文档",
"ApiEndpointFindAllPost": "带过滤条件查询(JSON 请求体)",
"ApiEndpointLoadModel": "加载数据模型",
"ApiEndpointPing": "健康检查",
"ApiEndpointTx": "创建或更新文档",
"ApiTokenCopyWarning": "请立即复制此令牌,之后将无法再次查看。",
"ApiTokenCreated": "令牌已创建",
"ApiTokenExpiry": "有效期",
"ApiTokenName": "令牌名称",
"ApiTokenNoTokens": "暂无 API 令牌",
"ApiTokenRevoke": "撤销令牌",
"ApiTokenRevokeConfirm": "确定要撤销此令牌吗?撤销后将无法再用于 API 访问。",
"ApiTokenWorkspace": "工作区",
"ApiTokens": "API 令牌",
"ApiUsageDescription": "将您的 API 令牌与内置 REST API 配合使用,以查询和修改工作区数据。在 Authorization 标头中以 Bearer 令牌形式传递该令牌。",
"ApiUsageTitle": "使用 REST API",
"ApiWorkspaceId": "您的工作区 ID(UUID)已包含在令牌中。在 URL 中将其作为 :workspaceId 传递。",
"CreateApiToken": "创建令牌",
"Created": "创建于",
"Expires": "过期时间",
"TokenStatus": "状态",
"ApiTokenStatusActive": "有效",
"ApiTokenStatusExpiring": "即将过期",
"ApiTokenStatusRevoked": "已撤销",
"ApiTokenStatusExpired": "已过期",
"ApiTokenExpiry7Days": "7 天",
"ApiTokenExpiry30Days": "30 天",
"ApiTokenExpiry90Days": "90 天",
"ApiTokenExpiry180Days": "180 天",
"ApiTokenExpiry365Days": "365 天",
"ApiTokenLoadError": "加载 API 令牌失败",
"ApiTokenCreateError": "创建令牌失败,请重试。",
"ApiTokenRevokeError": "撤销令牌失败,请重试。"
}
}