From 30d5e5268d7f0a43575248edcc7dee1b7a636725 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 10:40:10 +0000 Subject: [PATCH] fix(console,i18n): a settings row's chips read in the session's language (objectui#12821) The env-lock chip "Locked by env", its fallback title, the Deprecated chip and the unsupported-specifier row were JSX literals. They now read console.settingsView.* keys present in all ten packs; English output is unchanged. An enumeration pin over apps/console/src/pages/settings/*.tsx holds the class: no JSX text, rendered child string, title or aria-label is a bare literal. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB --- .changeset/12821-settings-chip-i18n.md | 11 ++ .../src/pages/settings/EnvLockBadge.tsx | 11 +- .../src/pages/settings/SettingsField.tsx | 9 +- .../SettingsField.chipLocale-12821.test.tsx | 171 ++++++++++++++++++ .../settingsPage.noBareLiterals-12821.test.ts | 141 +++++++++++++++ packages/i18n/src/locales/ar.ts | 5 + packages/i18n/src/locales/de.ts | 5 + packages/i18n/src/locales/en.ts | 10 + packages/i18n/src/locales/es.ts | 5 + packages/i18n/src/locales/fr.ts | 5 + packages/i18n/src/locales/ja.ts | 5 + packages/i18n/src/locales/ko.ts | 5 + packages/i18n/src/locales/pt.ts | 5 + packages/i18n/src/locales/ru.ts | 5 + packages/i18n/src/locales/zh.ts | 5 + 15 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 .changeset/12821-settings-chip-i18n.md create mode 100644 apps/console/src/pages/settings/__tests__/SettingsField.chipLocale-12821.test.tsx create mode 100644 apps/console/src/pages/settings/__tests__/settingsPage.noBareLiterals-12821.test.ts diff --git a/.changeset/12821-settings-chip-i18n.md b/.changeset/12821-settings-chip-i18n.md new file mode 100644 index 0000000000..7f09ae88ae --- /dev/null +++ b/.changeset/12821-settings-chip-i18n.md @@ -0,0 +1,11 @@ +--- +'@object-ui/i18n': patch +'@object-ui/console': patch +--- + +A settings row's chips read in the session's language (objectui#12821). The env-lock chip said "Locked by env" in every language, and its title, shown when the server names no reason, was English too; the `Deprecated` chip (with its replacement key) and the row drawn for an unsupported specifier type were English literals as well. + +- **`@object-ui/i18n`** adds `console.settingsView.envLockBadge`, `envLockBadgeTitle`, `deprecatedBadge`, `deprecatedBadgeReplacedBy` (with `{{replacedBy}}`) and `unsupportedSpecifierType` (with `{{type}}`) to all ten bundles. The English values are the old wording, so an English session reads exactly as before. +- **The console's settings page** draws those chips from these keys. A reason the server sends (`lockedReason`, such as "Set via env: NAME") is still shown as served. + +No export, prop or type is added, removed or renamed. diff --git a/apps/console/src/pages/settings/EnvLockBadge.tsx b/apps/console/src/pages/settings/EnvLockBadge.tsx index 8b12f228f8..fd50404c4b 100644 --- a/apps/console/src/pages/settings/EnvLockBadge.tsx +++ b/apps/console/src/pages/settings/EnvLockBadge.tsx @@ -1,9 +1,15 @@ /** * — small chip indicating the value is pinned by env. + * + * The chip and its fallback title are the settings page's own copy, so both + * read `console.settingsView.*` in the session's language (objectui#12821). + * A served `reason` is the server's text (`ResolvedSettingValue.lockedReason`) + * and is shown as served. */ import { Lock } from 'lucide-react'; import { Badge } from '@object-ui/components'; +import { useObjectTranslation } from '@object-ui/i18n'; export interface EnvLockBadgeProps { /** The reason string carried in ResolvedSettingValue.lockedReason. */ @@ -12,14 +18,15 @@ export interface EnvLockBadgeProps { } export function EnvLockBadge({ reason, className }: EnvLockBadgeProps) { + const { t } = useObjectTranslation(); return ( - Locked by env + {t('console.settingsView.envLockBadge')} ); } diff --git a/apps/console/src/pages/settings/SettingsField.tsx b/apps/console/src/pages/settings/SettingsField.tsx index 31178235c9..841439f51c 100644 --- a/apps/console/src/pages/settings/SettingsField.tsx +++ b/apps/console/src/pages/settings/SettingsField.tsx @@ -160,6 +160,7 @@ function FieldHeader({ labelId?: string; htmlFor?: string; }) { + const { t } = useObjectTranslation(); return (
{spec.deprecated ? ( - Deprecated{spec.replacedBy ? ` → ${spec.replacedBy}` : ''} + {spec.replacedBy + ? t('console.settingsView.deprecatedBadgeReplacedBy', { replacedBy: spec.replacedBy }) + : t('console.settingsView.deprecatedBadge')} ) : null} {resolved?.locked ? : null} @@ -646,7 +649,9 @@ export function SettingsField(props: SettingsFieldProps) { ); default: return wrapper( -
Unsupported specifier type: {spec.type}
, +
+ {t('console.settingsView.unsupportedSpecifierType', { type: spec.type })} +
, undefined, ); } diff --git a/apps/console/src/pages/settings/__tests__/SettingsField.chipLocale-12821.test.tsx b/apps/console/src/pages/settings/__tests__/SettingsField.chipLocale-12821.test.tsx new file mode 100644 index 0000000000..f031ac3e90 --- /dev/null +++ b/apps/console/src/pages/settings/__tests__/SettingsField.chipLocale-12821.test.tsx @@ -0,0 +1,171 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * The chips on a settings row's header speak the session locale + * (objectui#12821, the family after objectui#12694). + * + * ## What was broken + * + * `EnvLockBadge` drew its chip text and its fallback title as JSX literals, and + * `FieldHeader` drew the `Deprecated` chip (and its `→ replacedBy` arrow) the + * same way. So an administrator on a zh-CN session read "Locked by env" beside + * a row that otherwise read in zh. All three now read `console.settingsView.*`. + * + * ## What stays as served + * + * A served `lockedReason` (the server's "Set via env: NAME") is the server's + * text and is shown verbatim as the title; only the fallback title, used when + * the server names no reason, is the page's own copy. + * + * ## The `Deprecated` chip has no producer today + * + * No platform settings manifest declares a deprecated specifier, so nothing + * draws this chip in a running console. It is the same class on the same row, + * so a fixture specifier draws it here. + * + * ## Red-first prediction (written BEFORE the first run) + * + * On unchanged `main`: the zh cases and the every-pack case FAIL (the chip + * carries the English literal; no pack defines the keys); the en cases PASS, + * because English is the one language whose output must not change. + */ + +import { describe, it, expect, afterEach, beforeEach } from 'vitest'; +import { cleanup, render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { I18nProvider } from '@object-ui/i18n'; +import { builtInLocales } from '@object-ui/i18n/locales'; +import { SettingsField } from '../SettingsField'; +import type { ResolvedSettingValue, Specifier } from '../types'; + +afterEach(cleanup); +// A language a previous case persisted would outrank `defaultLanguage`. +beforeEach(() => { + window.localStorage.clear(); +}); + +const COUNTRY: Specifier = { + type: 'text', + key: 'default_country', + label: 'Default country', + required: false, +}; + +const DEPRECATED: Specifier = { ...COUNTRY, deprecated: true }; +const DEPRECATED_REPLACED: Specifier = { ...COUNTRY, deprecated: true, replacedBy: 'country_code' }; + +/** Pinned by env, the server naming no reason: the chip shows the fallback title. */ +const LOCKED_NO_REASON: ResolvedSettingValue = { value: 'CN', source: 'env', locked: true }; + +/** Pinned by env with a served reason: the server's text is the title, verbatim. */ +const LOCKED_WITH_REASON: ResolvedSettingValue = { + value: 'CN', + source: 'env', + locked: true, + lockedReason: 'Set via env: OS_LOCALIZATION_DEFAULT_COUNTRY', +}; + +type Lang = 'en' | 'zh-CN'; + +function renderRow(language: Lang, spec: Specifier, resolved?: ResolvedSettingValue) { + return render( + + {}} /> + , + ); +} + +const view = builtInLocales.zh.console.settingsView as unknown as Record; +const viewEn = builtInLocales.en.console.settingsView as unknown as Record; +const LATIN = /\p{Script=Latin}/u; + +/** The row header's one chip whose text is `text`, once the catalogue has landed. */ +async function chip(text: string) { + return waitFor(() => screen.getByText(text, { selector: 'div,span' })); +} + +describe('the env-lock chip under zh-CN reads in zh (objectui#12821)', () => { + it('the chip text and its fallback title are the zh strings', async () => { + renderRow('zh-CN', COUNTRY, LOCKED_NO_REASON); + const el = await chip(view.envLockBadge); + expect(el.textContent).not.toMatch(LATIN); + expect(el).toHaveAttribute('title', view.envLockBadgeTitle); + expect(el.getAttribute('title')).not.toMatch(LATIN); + }); + + it("a served reason is the server's text, shown as served", async () => { + renderRow('zh-CN', COUNTRY, LOCKED_WITH_REASON); + const el = await chip(view.envLockBadge); + expect(el).toHaveAttribute('title', 'Set via env: OS_LOCALIZATION_DEFAULT_COUNTRY'); + }); +}); + +describe('the Deprecated chip under zh-CN reads in zh (objectui#12821)', () => { + it('without a replacement', async () => { + renderRow('zh-CN', DEPRECATED); + const el = await chip(view.deprecatedBadge); + expect(el.textContent).not.toMatch(LATIN); + }); + + it('with a replacement, the key spliced into the zh sentence', async () => { + renderRow('zh-CN', DEPRECATED_REPLACED); + const expected = view.deprecatedBadgeReplacedBy.replace('{{replacedBy}}', 'country_code'); + const el = await chip(expected); + // The replacement key is a key, not copy: the rest of the chip is zh. + expect(el.textContent!.replace('country_code', '')).not.toMatch(LATIN); + }); +}); + +describe('the chips under en read exactly as they did (objectui#12821, must-not-change)', () => { + it('Locked by env, with the old fallback title', async () => { + renderRow('en', COUNTRY, LOCKED_NO_REASON); + const el = await chip('Locked by env'); + expect(el.textContent).toBe('Locked by env'); + expect(el).toHaveAttribute('title', 'Pinned by environment variable — cannot be edited from the UI.'); + }); + + it('Deprecated, and Deprecated → replacedBy', async () => { + renderRow('en', DEPRECATED); + expect((await chip('Deprecated')).textContent).toBe('Deprecated'); + cleanup(); + renderRow('en', DEPRECATED_REPLACED); + expect((await chip('Deprecated → country_code')).textContent).toBe('Deprecated → country_code'); + }); + + it('the unsupported-type row reads as it did', async () => { + renderRow('en', { ...COUNTRY, type: 'not_a_type' } as unknown as Specifier); + expect((await chip('Unsupported specifier type: not_a_type')).textContent).toBe( + 'Unsupported specifier type: not_a_type', + ); + }); +}); + +describe('every pack carries the row chips, translated (objectui#12821)', () => { + const PLAIN = ['envLockBadge', 'envLockBadgeTitle', 'deprecatedBadge'] as const; + const HOLED = [ + ['deprecatedBadgeReplacedBy', '{{replacedBy}}'], + ['unsupportedSpecifierType', '{{type}}'], + ] as const; + + it.each(Object.keys(builtInLocales))('%s: each chip key is a string, not an English copy', (code) => { + const pack = builtInLocales[code as keyof typeof builtInLocales] as unknown as { + console?: { settingsView?: Record }; + }; + const at = (key: string) => pack.console?.settingsView?.[key]; + for (const key of PLAIN) { + expect(typeof at(key), `${code} console.settingsView.${key}`).toBe('string'); + if (code !== 'en') expect(at(key), `${code} ${key}`).not.toBe(viewEn[key]); + } + for (const [key, hole] of HOLED) { + expect(typeof at(key), `${code} console.settingsView.${key}`).toBe('string'); + expect((at(key) as string).split(hole), `${code} ${key} holes`).toHaveLength(2); + if (code !== 'en') expect(at(key), `${code} ${key}`).not.toBe(viewEn[key]); + } + }); +}); diff --git a/apps/console/src/pages/settings/__tests__/settingsPage.noBareLiterals-12821.test.ts b/apps/console/src/pages/settings/__tests__/settingsPage.noBareLiterals-12821.test.ts new file mode 100644 index 0000000000..dccfc88e6f --- /dev/null +++ b/apps/console/src/pages/settings/__tests__/settingsPage.noBareLiterals-12821.test.ts @@ -0,0 +1,141 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * objectui#12821 — no component of the console settings page draws a bare + * English literal: every JSX text node, every string a JSX child expression + * renders, and every `title` / `aria-label` value goes through `t()`. + * + * A row chip drawn as a literal reads English on every other language and + * nothing fails. That happened twice on this page: the source badges' + * "Overrides" / "Inherited from" (objectui#12694) and the env-lock chip + * "Locked by env" (objectui#12821). This pin holds the class for the page + * rather than one chip, so the next chip added cannot ship English-only. + * + * ## How the files are read + * + * Every `*.tsx` directly under `apps/console/src/pages/settings/`, listed from + * disk (a new component is enumerated with no edit here). Comments are blanked + * by `scripts/js-comment-mask.mjs`, the one comment masker this tree uses, + * before the TypeScript parser reads the result, so prose naming a literal is + * never a finding. + * + * ## What counts as a rendered literal + * + * A string with a letter in it (`\p{L}`, so any script, not only Latin) that + * reaches the DOM: a JSX text node, or a string in a RENDERED position of a JSX + * child expression or a `title` / `aria-label` value. A rendered position is + * the expression itself, a branch of `?:`, either side of `??` / `||`, the + * right of `&&`, or a template's text. A string inside a call (`t('key')`, + * `t(key, { defaultValue })`) or a comparison is not rendered, and a + * `className` is not copy. + */ +import { describe, it, expect } from 'vitest'; +import { readFileSync, readdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import ts from 'typescript'; +import { maskComments } from '../../../../../../scripts/js-comment-mask.mjs'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const PAGE_DIR = join(HERE, '..'); +const LETTER = /\p{L}/u; +const COPY_ATTRIBUTES = new Set(['title', 'aria-label']); + +function renderedLiterals(file: string, raw: string): string[] { + const source = maskComments(raw); + const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); + const hits: string[] = []; + const hit = (where: string, text: string) => { + if (LETTER.test(text)) hits.push(`${where}: ${JSON.stringify(text.trim())}`); + }; + + const rendered = (node: ts.Expression, where: string): void => { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return hit(where, node.text); + if (ts.isTemplateExpression(node)) { + hit(where, node.head.text); + for (const span of node.templateSpans) { + rendered(span.expression, where); + hit(where, span.literal.text); + } + return; + } + if (ts.isParenthesizedExpression(node)) return rendered(node.expression, where); + if (ts.isConditionalExpression(node)) { + rendered(node.whenTrue, where); + rendered(node.whenFalse, where); + return; + } + if (ts.isBinaryExpression(node)) { + const op = node.operatorToken.kind; + if (op === ts.SyntaxKind.QuestionQuestionToken || op === ts.SyntaxKind.BarBarToken) { + rendered(node.left, where); + rendered(node.right, where); + } else if (op === ts.SyntaxKind.AmpersandAmpersandToken) { + rendered(node.right, where); + } + } + }; + + const walk = (node: ts.Node): void => { + if (ts.isJsxText(node)) hit('text', node.text); + if ( + ts.isJsxExpression(node) && + node.expression && + (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent)) + ) { + rendered(node.expression, 'child'); + } + if (ts.isJsxAttribute(node) && node.initializer) { + const name = node.name.getText(sf); + if (COPY_ATTRIBUTES.has(name)) { + const init = node.initializer; + if (ts.isStringLiteral(init)) hit(name, init.text); + else if (ts.isJsxExpression(init) && init.expression) rendered(init.expression, name); + } + } + ts.forEachChild(node, walk); + }; + walk(sf); + return hits; +} + +const FILES = readdirSync(PAGE_DIR) + .filter((name) => name.endsWith('.tsx')) + .sort(); + +describe('the console settings page draws no bare English literal (objectui#12821)', () => { + it('enumerates the page components (a short list would pass vacuously)', () => { + expect(FILES).toContain('EnvLockBadge.tsx'); + expect(FILES).toContain('SettingsField.tsx'); + }); + + it('the detector sees the shapes it claims, and not a t() call or a comment (control)', () => { + const sample = [ + 'export const A = ({ r, n }) => (', + " ", + ' Locked by env', + " {n ? `Deprecated → ${n}` : ''}", + " {t('console.settingsView.envLockBadge', { defaultValue: 'Locked by env' })}", + " ", + " {/* Locked by env */}", + ' ', + ');', + ].join('\n'); + expect(renderedLiterals('sample.tsx', sample)).toEqual([ + 'title: "Pinned by env"', + 'aria-label: "Lock"', + 'text: "Locked by env"', + 'child: "Deprecated →"', + ]); + }); + + it.each(FILES)('%s: no JSX text, rendered child string, title or aria-label is a literal', (file) => { + expect(renderedLiterals(file, readFileSync(join(PAGE_DIR, file), 'utf8'))).toEqual([]); + }); +}); diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts index 0c6af82cde..bae24daaa8 100644 --- a/packages/i18n/src/locales/ar.ts +++ b/packages/i18n/src/locales/ar.ts @@ -2225,6 +2225,11 @@ const ar = { cryptoRefusalToastNoSubject: "تعذّر تشفير الأسرار", sourceBadgeInherited: "موروث من {{source}}", sourceBadgeOverrides: "يتجاوز {{source}}", + envLockBadge: "مقفل بواسطة البيئة", + envLockBadgeTitle: "مثبّت بواسطة متغير بيئة — لا يمكن تعديله من الواجهة.", + deprecatedBadge: "مهمل", + deprecatedBadgeReplacedBy: "مهمل ← {{replacedBy}}", + unsupportedSpecifierType: "نوع إعداد غير مدعوم: {{type}}", }, workspaceTimezonePrompt: { title: "تعيين المنطقة الزمنية للبيئة", diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index 320522cf64..3d3d23b766 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -2028,6 +2028,11 @@ const de = { cryptoRefusalToastNoSubject: "Geheimnisse können nicht verschlüsselt werden", sourceBadgeInherited: "Geerbt von {{source}}", sourceBadgeOverrides: "Überschreibt {{source}}", + envLockBadge: "Per Umgebung gesperrt", + envLockBadgeTitle: "Durch eine Umgebungsvariable festgelegt — kann nicht in der Oberfläche bearbeitet werden.", + deprecatedBadge: "Veraltet", + deprecatedBadgeReplacedBy: "Veraltet → {{replacedBy}}", + unsupportedSpecifierType: "Nicht unterstützter Einstellungstyp: {{type}}", }, workspaceTimezonePrompt: { title: "Zeitzone der Umgebung festlegen", diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index 6a1d3883e9..35661a9e28 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -2161,6 +2161,16 @@ const en = { // badge, so a pack puts the layer where its own grammar wants it. sourceBadgeInherited: 'Inherited from {{source}}', sourceBadgeOverrides: 'Overrides {{source}}', + // The chips on a settings row's header (objectui#12821): the env-lock + // chip and the title it shows when the server sends no reason, and the + // `Deprecated` chip, with `{{replacedBy}}` the key that replaces it. + // `unsupportedSpecifierType` is the row drawn for a specifier type the + // page has no control for, `{{type}}` that type. + envLockBadge: 'Locked by env', + envLockBadgeTitle: 'Pinned by environment variable — cannot be edited from the UI.', + deprecatedBadge: 'Deprecated', + deprecatedBadgeReplacedBy: 'Deprecated → {{replacedBy}}', + unsupportedSpecifierType: 'Unsupported specifier type: {{type}}', }, // The one-time workspace-timezone prompt (objectui#11758): asked of an // administrator while `localization.timezone` is still the manifest default. diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts index 95d3988728..4fcc9204bd 100644 --- a/packages/i18n/src/locales/es.ts +++ b/packages/i18n/src/locales/es.ts @@ -2077,6 +2077,11 @@ const es = { cryptoRefusalToastNoSubject: "No se pueden cifrar los secretos", sourceBadgeInherited: "Heredado de {{source}}", sourceBadgeOverrides: "Sobrescribe {{source}}", + envLockBadge: "Bloqueado por el entorno", + envLockBadgeTitle: "Fijado por una variable de entorno: no se puede editar desde la interfaz.", + deprecatedBadge: "Obsoleto", + deprecatedBadgeReplacedBy: "Obsoleto → {{replacedBy}}", + unsupportedSpecifierType: "Tipo de ajuste no admitido: {{type}}", }, workspaceTimezonePrompt: { title: "Configure la zona horaria del entorno", diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts index cf6ced52af..e0d0c03049 100644 --- a/packages/i18n/src/locales/fr.ts +++ b/packages/i18n/src/locales/fr.ts @@ -2074,6 +2074,11 @@ const fr = { cryptoRefusalToastNoSubject: "Impossible de chiffrer les secrets", sourceBadgeInherited: "Hérité de {{source}}", sourceBadgeOverrides: "Remplace {{source}}", + envLockBadge: "Verrouillé par l'env.", + envLockBadgeTitle: "Fixé par une variable d'environnement — modification impossible depuis l'interface.", + deprecatedBadge: "Obsolète", + deprecatedBadgeReplacedBy: "Obsolète → {{replacedBy}}", + unsupportedSpecifierType: "Type de paramètre non pris en charge : {{type}}", }, workspaceTimezonePrompt: { title: "Définir le fuseau horaire de l’environnement", diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts index 4c83cc9819..1a36d01303 100644 --- a/packages/i18n/src/locales/ja.ts +++ b/packages/i18n/src/locales/ja.ts @@ -2030,6 +2030,11 @@ const ja = { cryptoRefusalToastNoSubject: "シークレットを暗号化できません", sourceBadgeInherited: "{{source}}から継承", sourceBadgeOverrides: "{{source}}を上書き", + envLockBadge: "環境変数でロック", + envLockBadgeTitle: "環境変数で固定されているため、画面から編集できません。", + deprecatedBadge: "非推奨", + deprecatedBadgeReplacedBy: "非推奨 → {{replacedBy}}", + unsupportedSpecifierType: "サポートされていない設定項目の種類: {{type}}", }, workspaceTimezonePrompt: { title: "環境のタイムゾーンを設定", diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts index 5652295552..2a81cd0040 100644 --- a/packages/i18n/src/locales/ko.ts +++ b/packages/i18n/src/locales/ko.ts @@ -2028,6 +2028,11 @@ const ko = { cryptoRefusalToastNoSubject: "시크릿을 암호화할 수 없습니다", sourceBadgeInherited: "{{source}}에서 상속됨", sourceBadgeOverrides: "{{source}} 재정의", + envLockBadge: "환경 변수로 잠김", + envLockBadgeTitle: "환경 변수로 고정되어 UI에서 편집할 수 없습니다.", + deprecatedBadge: "지원 중단됨", + deprecatedBadgeReplacedBy: "지원 중단됨 → {{replacedBy}}", + unsupportedSpecifierType: "지원되지 않는 설정 항목 유형: {{type}}", }, workspaceTimezonePrompt: { title: "환경 시간대 설정", diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts index 4c39fd41d2..5f70c08aab 100644 --- a/packages/i18n/src/locales/pt.ts +++ b/packages/i18n/src/locales/pt.ts @@ -2072,6 +2072,11 @@ const pt = { cryptoRefusalToastNoSubject: "Não é possível criptografar segredos", sourceBadgeInherited: "Herdado de {{source}}", sourceBadgeOverrides: "Substitui {{source}}", + envLockBadge: "Bloqueado pelo ambiente", + envLockBadgeTitle: "Fixado por uma variável de ambiente — não pode ser editado pela interface.", + deprecatedBadge: "Obsoleto", + deprecatedBadgeReplacedBy: "Obsoleto → {{replacedBy}}", + unsupportedSpecifierType: "Tipo de configuração não suportado: {{type}}", }, workspaceTimezonePrompt: { title: "Definir o fuso horário do ambiente", diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts index ca5b870baf..5fbb744abe 100644 --- a/packages/i18n/src/locales/ru.ts +++ b/packages/i18n/src/locales/ru.ts @@ -2135,6 +2135,11 @@ const ru = { cryptoRefusalToastNoSubject: "Не удалось зашифровать секреты", sourceBadgeInherited: "Унаследовано: {{source}}", sourceBadgeOverrides: "Переопределяет: {{source}}", + envLockBadge: "Задано окружением", + envLockBadgeTitle: "Закреплено переменной окружения — изменить в интерфейсе нельзя.", + deprecatedBadge: "Устарело", + deprecatedBadgeReplacedBy: "Устарело → {{replacedBy}}", + unsupportedSpecifierType: "Неподдерживаемый тип настройки: {{type}}", }, workspaceTimezonePrompt: { title: "Задайте часовой пояс окружения", diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts index fe4e830ca7..08adae6933 100644 --- a/packages/i18n/src/locales/zh.ts +++ b/packages/i18n/src/locales/zh.ts @@ -1818,6 +1818,11 @@ const zh = { cryptoRefusalToastNoSubject: '无法加密机密信息', sourceBadgeInherited: '继承自{{source}}', sourceBadgeOverrides: '覆盖{{source}}', + envLockBadge: '环境变量锁定', + envLockBadgeTitle: '由环境变量固定,无法在界面中编辑。', + deprecatedBadge: '已弃用', + deprecatedBadgeReplacedBy: '已弃用 → {{replacedBy}}', + unsupportedSpecifierType: '不支持的设置项类型:{{type}}', }, workspaceTimezonePrompt: { title: '设置环境时区',