Preserve core workflow terms in English and apply CJK spacing (#5141)
* Preserve core workflow and product terms in English across locales Update translation policy to prevent localization of key terms such as "Agent", "Commit", "Markdown", and "Terminal". This ensures consistent jargon and product branding. Introduce CJK-Latin term spacing to keep these Latin terms legible when combined with CJK text, while adjusting Korean particle spacing. Also add overrides to prevent network proxy settings from being mistranslated as "Agent". * Preserve repo terminology in English and localize source control labels Treat "repo" and "repos" (and their capitalized forms) as brand terms that should remain in English/Latin across CJK and Spanish locales. Update translation files and policies to replace translated words like "repositorio" or "リポジトリ" with "repo"/"repos", and fix an issue where latin brand terms could be incorrectly matched as substrings in larger words during cleanup. Additionally, externalize and localize the "Staged Changes", "Changes", and "Untracked Files" section labels in the source control sidebar.
This commit is contained in:
parent
d66027c433
commit
d4bee7db5c
|
|
@ -134,6 +134,8 @@ export const JA_VALUE_OVERRIDES = {
|
|||
'Add a project first': 'まずプロジェクトを追加',
|
||||
'Pick a base branch below': '以下のベースブランチを選択',
|
||||
'Choose floating workspace directory': 'フローティング ワークスペース ディレクトリを選択',
|
||||
'Local project, Git repo, or folder with many repos':
|
||||
'ローカルプロジェクト、Git repo、または多数の repos を含むフォルダー',
|
||||
'Enter passphrase': 'パスフレーズを入力',
|
||||
'Enter password': 'パスワードを入力',
|
||||
'Enter the passphrase for': 'のパスフレーズを入力',
|
||||
|
|
|
|||
|
|
@ -204,6 +204,8 @@ export const KO_VALUE_OVERRIDES = {
|
|||
'to submit.': '제출.',
|
||||
'Install the Orca skill so agents know to use the Orca CLI.':
|
||||
'에이전트가 Orca CLI를 사용하도록 Orca 스킬을 설치하세요.',
|
||||
'Local project, Git repo, or folder with many repos':
|
||||
'로컬 프로젝트, Git repo 또는 repos가 많은 폴더',
|
||||
'Linear, GitLab, Bitbucket, Azure DevOps, Gitea, and Jira live in Settings > Integrations.':
|
||||
'Linear, GitLab, Bitbucket, Azure DevOps, Gitea 및 Jira는 설정 > 연동에 있습니다.',
|
||||
'changed since you last approved. Re-review before it runs':
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ describe('locale-translation-policy ko round 5', () => {
|
|||
localeValue: '에이전트가 Orca CLI 사용 방법을 알 수 있도록 Orca 기술을 설치합니다.',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('에이전트가 Orca CLI를 사용하도록 Orca 스킬을 설치하세요.')
|
||||
).toBe('agents가 Orca CLI를 사용하도록 Orca 스킬을 설치하세요.')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.editor.MarkdownPreview.322afab6ff',
|
||||
|
|
@ -83,4 +83,55 @@ describe('locale-translation-policy ko round 5', () => {
|
|||
'작업 중단, 오래 생성된 파일, 유효성 검사 실패 및 사람의 주의가 필요한 모든 사항을 확인하세요. 실행 가능한 이슈만 보고하세요.'
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps protected workflow terms in English', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.feature.wall.BrowserAnimatedVisual.04096318ab',
|
||||
enValue: 'Terminal 1',
|
||||
localeValue: '터미널 1',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('Terminal 1')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.skills.SkillsPage.38e0951c3a',
|
||||
enValue: 'Agent Skills',
|
||||
localeValue: '에이전트 스킬',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('Agent 스킬')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.LinearIssueMarkdownDescriptionEditor.d9c47069ef',
|
||||
enValue: 'Markdown',
|
||||
localeValue: '가격 인하',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('Markdown')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.9623a5107d',
|
||||
enValue: 'Unpushed commits',
|
||||
localeValue: '푸시되지 않은 커밋',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('푸시되지 않은 commits')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.0b1766738a',
|
||||
enValue: 'Repo',
|
||||
localeValue: '레포',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('Repo')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.sidebar.add.repo.local.start.actions.fb4fc5380e',
|
||||
enValue: 'Local project, Git repo, or folder with many repos',
|
||||
localeValue: '로컬 프로젝트, Git 저장소 또는 저장소가 많은 폴더',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('로컬 프로젝트, Git repo 또는 repos가 많은 폴더')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { repairTranslatedValue } from './locale-translation-policy.mjs'
|
||||
|
||||
describe('locale-translation-policy es round 5', () => {
|
||||
it('keeps protected workflow terms in English', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.LinearIssueMarkdownDescriptionEditor.d9c47069ef',
|
||||
enValue: 'Markdown',
|
||||
localeValue: 'Reducción',
|
||||
locale: 'es'
|
||||
})
|
||||
).toBe('Markdown')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.TaskPage.7f3f7b4c18',
|
||||
enValue: 'Description (optional, markdown)',
|
||||
localeValue: 'Descripción (opcional, rebaja)',
|
||||
locale: 'es'
|
||||
})
|
||||
).toBe('Descripción (opcional, markdown)')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.right.sidebar.source.control.primary.action.ed93b4f14f',
|
||||
enValue: 'Commit',
|
||||
localeValue: 'Comprometerse',
|
||||
locale: 'es'
|
||||
})
|
||||
).toBe('Commit')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.right.sidebar.GitHistoryPanel.cf7cad58d2',
|
||||
enValue: 'No commits yet',
|
||||
localeValue: 'Aún no hay compromisos',
|
||||
locale: 'es'
|
||||
})
|
||||
).toBe('Aún no hay commits')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.right.sidebar.SourceControl.b94112eb9e',
|
||||
enValue: 'Commit message',
|
||||
localeValue: 'mensaje de confirmación',
|
||||
locale: 'es'
|
||||
})
|
||||
).toBe('mensaje de Commit')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.0b1766738a',
|
||||
enValue: 'Repo',
|
||||
localeValue: 'repositorio',
|
||||
locale: 'es'
|
||||
})
|
||||
).toBe('Repo')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.sidebar.add.repo.local.start.actions.fb4fc5380e',
|
||||
enValue: 'Local project, Git repo, or folder with many repos',
|
||||
localeValue: 'Proyecto local, repositorio de Git o carpeta con muchos repositorios',
|
||||
locale: 'es'
|
||||
})
|
||||
).toBe('Proyecto local, repo de Git o carpeta con muchos repos')
|
||||
})
|
||||
})
|
||||
|
|
@ -101,4 +101,56 @@ describe('locale-translation-policy ja round 5', () => {
|
|||
})
|
||||
).toBe('まずプロジェクトを追加')
|
||||
})
|
||||
|
||||
it('keeps protected workflow terms in English', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.status.bar.WorkspaceSpaceManagerPanel.e9528a89b3',
|
||||
enValue: 'Terminals',
|
||||
localeValue: '端子',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('Terminals')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.skills.SkillsPage.38e0951c3a',
|
||||
enValue: 'Agent Skills',
|
||||
localeValue: 'エージェントのスキル',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('Agent のスキル')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.tab.bar.TabBar.3d5d6c960d',
|
||||
enValue: 'New Markdown',
|
||||
localeValue: '新規マークダウン',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('新規 Markdown')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.sidebar.local.base.ref.suggestion.toast.commits',
|
||||
enValue: 'commits',
|
||||
localeValue: 'コミット',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('commits')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.mobile.slides.WorktreeListSlide.22971156df',
|
||||
enValue: 'Repo',
|
||||
localeValue: 'リポ',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('Repo')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.sidebar.add.repo.local.start.actions.fb4fc5380e',
|
||||
enValue: 'Local project, Git repo, or folder with many repos',
|
||||
localeValue:
|
||||
'ローカル プロジェクト、Git リポジトリ、または多数のリポジトリを含むフォルダー',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('ローカルプロジェクト、Git repo、または多数の repos を含むフォルダー')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ const OPEN_IN_APP_CATALOG_PREFIX = 'auto.lib.open.in.app.catalog.'
|
|||
export const ENGLISH_ONLY_KEY_PREFIXES = [AGENT_CATALOG_PREFIX, OPEN_IN_APP_CATALOG_PREFIX]
|
||||
|
||||
export const NEVER_TRANSLATE_VALUES = new Set([
|
||||
'Agent',
|
||||
'Agents',
|
||||
'Aider',
|
||||
'Amp',
|
||||
'Antigravity',
|
||||
|
|
@ -48,12 +50,28 @@ export const NEVER_TRANSLATE_VALUES = new Set([
|
|||
'Pi',
|
||||
'PostHog',
|
||||
'Qwen Code',
|
||||
'Repo',
|
||||
'Repos',
|
||||
'Rovo Dev',
|
||||
'Commit',
|
||||
'Commits',
|
||||
'Markdown',
|
||||
'Terminal',
|
||||
'Terminals',
|
||||
'VS Code',
|
||||
'Zed',
|
||||
'agent',
|
||||
'agents',
|
||||
'codex',
|
||||
'commit',
|
||||
'commits',
|
||||
'gemini',
|
||||
'claude',
|
||||
'markdown',
|
||||
'repo',
|
||||
'repos',
|
||||
'terminal',
|
||||
'terminals',
|
||||
'gh',
|
||||
'idle',
|
||||
'anthropic',
|
||||
|
|
@ -63,6 +81,7 @@ export const NEVER_TRANSLATE_VALUES = new Set([
|
|||
'darwin',
|
||||
'Nautilus',
|
||||
'GitHub',
|
||||
'no_proxy',
|
||||
'Beta'
|
||||
])
|
||||
|
||||
|
|
@ -84,7 +103,25 @@ export const BRAND_MISTRANSLATIONS = {
|
|||
Pi: ['파이'],
|
||||
'GitHub Copilot': ['GitHub 코파일럿', '코파일럿'],
|
||||
Discord: ['디스코드'],
|
||||
Linear: ['선형']
|
||||
Linear: ['선형'],
|
||||
Agent: ['에이전트'],
|
||||
Agents: ['에이전트'],
|
||||
agent: ['에이전트'],
|
||||
agents: ['에이전트'],
|
||||
Commit: ['커밋'],
|
||||
Commits: ['커밋'],
|
||||
commit: ['커밋'],
|
||||
commits: ['커밋'],
|
||||
Markdown: ['마크다운', '가격 인하'],
|
||||
markdown: ['마크다운', '가격 인하'],
|
||||
Repo: ['저장소', '레포'],
|
||||
Repos: ['저장소', '레포'],
|
||||
repo: ['저장소', '레포'],
|
||||
repos: ['저장소', '레포'],
|
||||
Terminal: ['터미널'],
|
||||
Terminals: ['터미널'],
|
||||
terminal: ['터미널'],
|
||||
terminals: ['터미널']
|
||||
},
|
||||
zh: {
|
||||
Codex: ['法典'],
|
||||
|
|
@ -104,7 +141,25 @@ export const BRAND_MISTRANSLATIONS = {
|
|||
Droid: ['机器人'],
|
||||
'GitHub Copilot': ['GitHub 副驾驶', '副驾驶'],
|
||||
Linear: ['线性', '线形'],
|
||||
Jira: ['吉拉']
|
||||
Jira: ['吉拉'],
|
||||
Agent: ['代理', '智能体'],
|
||||
Agents: ['代理', '智能体'],
|
||||
agent: ['代理', '智能体'],
|
||||
agents: ['代理', '智能体'],
|
||||
Commit: ['提交'],
|
||||
Commits: ['提交'],
|
||||
commit: ['提交'],
|
||||
commits: ['提交'],
|
||||
Markdown: ['降价'],
|
||||
markdown: ['降价'],
|
||||
Repo: ['存储库', '仓库', '回购协议', '回购'],
|
||||
Repos: ['存储库', '仓库', '回购协议', '回购'],
|
||||
repo: ['存储库', '仓库', '回购协议', '回购'],
|
||||
repos: ['存储库', '仓库', '回购协议', '回购'],
|
||||
Terminal: ['终端', '端子'],
|
||||
Terminals: ['终端', '端子'],
|
||||
terminal: ['终端', '端子'],
|
||||
terminals: ['终端', '端子']
|
||||
},
|
||||
ja: {
|
||||
Codex: ['法典', 'コーデックス'],
|
||||
|
|
@ -124,7 +179,25 @@ export const BRAND_MISTRANSLATIONS = {
|
|||
Droid: ['ロボット', 'ドロイド'],
|
||||
'GitHub Copilot': ['GitHub コパイロット', 'コパイロット'],
|
||||
Discord: ['不和'],
|
||||
Linear: ['線形']
|
||||
Linear: ['線形'],
|
||||
Agent: ['エージェント'],
|
||||
Agents: ['エージェント'],
|
||||
agent: ['エージェント'],
|
||||
agents: ['エージェント'],
|
||||
Commit: ['コミット'],
|
||||
Commits: ['コミット'],
|
||||
commit: ['コミット'],
|
||||
commits: ['コミット'],
|
||||
Markdown: ['マークダウン'],
|
||||
markdown: ['マークダウン'],
|
||||
Repo: ['リポジトリ', 'リポ'],
|
||||
Repos: ['リポジトリ', 'リポ'],
|
||||
repo: ['リポジトリ', 'リポ'],
|
||||
repos: ['リポジトリ', 'リポ'],
|
||||
Terminal: ['ターミナル', '端子'],
|
||||
Terminals: ['ターミナル', '端子'],
|
||||
terminal: ['ターミナル', '端子'],
|
||||
terminals: ['ターミナル', '端子']
|
||||
},
|
||||
es: {
|
||||
Codex: ['códice', 'Códice'],
|
||||
|
|
@ -137,7 +210,21 @@ export const BRAND_MISTRANSLATIONS = {
|
|||
'GitHub Copilot': ['Copiloto de GitHub'],
|
||||
Discord: ['discordia'],
|
||||
Linear: ['lineal', 'Lineal'],
|
||||
Jira: ['jira']
|
||||
Jira: ['jira'],
|
||||
Agent: ['Agente', 'agente'],
|
||||
Agents: ['Agentes', 'agentes'],
|
||||
agent: ['agente'],
|
||||
agents: ['agentes'],
|
||||
Commit: ['Confirmación', 'confirmación', 'Confirmar', 'Comprometerse'],
|
||||
Commits: ['Confirmaciones', 'confirmaciones', 'Compromisos', 'compromisos', 'Se compromete'],
|
||||
commit: ['confirmación', 'confirmar', 'comprometerse', 'compromiso'],
|
||||
commits: ['confirmaciones', 'compromisos'],
|
||||
Markdown: ['Reducción', 'reducción', 'Rebaja', 'rebaja', 'rebajas'],
|
||||
markdown: ['reducción', 'rebaja', 'rebajas'],
|
||||
Repo: ['Repositorio', 'repositorio'],
|
||||
Repos: ['Repositorios', 'repositorios'],
|
||||
repo: ['repositorio'],
|
||||
repos: ['repositorios']
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,6 +235,34 @@ export const NATIVE_PICKER_LABELS = {
|
|||
es: { chinese: '中文(简体)', korean: '한국어', japanese: '日本語', spanish: 'Español' }
|
||||
}
|
||||
|
||||
const CJK_LATIN_SPACED_TERMS = [
|
||||
'Terminal',
|
||||
'Terminals',
|
||||
'terminal',
|
||||
'terminals',
|
||||
'Agents',
|
||||
'Agent',
|
||||
'agents',
|
||||
'agent',
|
||||
'Markdown',
|
||||
'markdown',
|
||||
'Repos',
|
||||
'Repo',
|
||||
'repos',
|
||||
'repo',
|
||||
'Commits',
|
||||
'Commit',
|
||||
'commits',
|
||||
'commit',
|
||||
'Linear',
|
||||
'Claude',
|
||||
'Codex',
|
||||
'Orca',
|
||||
'Cursor'
|
||||
]
|
||||
|
||||
const CJK_LATIN_SPACED_TERM_PATTERN = CJK_LATIN_SPACED_TERMS.join('|')
|
||||
|
||||
export function isEnglishOnlyKey(key) {
|
||||
return ENGLISH_ONLY_KEY_PREFIXES.some((prefix) => key.startsWith(prefix))
|
||||
}
|
||||
|
|
@ -165,15 +280,28 @@ export function shouldPreserveEnglishValue(enValue, key = '') {
|
|||
return NEVER_TRANSLATE_VALUES.has(enValue)
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
function includesPreservedLatinTerm(value, term) {
|
||||
if (!/^[A-Za-z_]+$/.test(term)) {
|
||||
return value.includes(term)
|
||||
}
|
||||
return new RegExp(`(^|[^A-Za-z_])${escapeRegExp(term)}($|[^A-Za-z_])`).test(value)
|
||||
}
|
||||
|
||||
function applyBrandMistranslationFixes(enValue, localeValue, locale) {
|
||||
let result = localeValue
|
||||
const mistranslations = BRAND_MISTRANSLATIONS[locale] ?? {}
|
||||
|
||||
for (const [brand, wrongForms] of Object.entries(mistranslations)) {
|
||||
for (const [brand, wrongForms] of Object.entries(mistranslations).sort(
|
||||
([left], [right]) => right.length - left.length
|
||||
)) {
|
||||
if (!enValue.includes(brand)) {
|
||||
continue
|
||||
}
|
||||
if (result.includes(brand)) {
|
||||
if (includesPreservedLatinTerm(result, brand)) {
|
||||
continue
|
||||
}
|
||||
for (const wrong of wrongForms) {
|
||||
|
|
@ -191,6 +319,39 @@ function applyBrandMistranslationFixes(enValue, localeValue, locale) {
|
|||
return result
|
||||
}
|
||||
|
||||
function applyCjkLatinTermSpacing(localeValue, locale) {
|
||||
// Why: CJK UI copy should keep protected Latin workflow terms readable when MT glues them to native text.
|
||||
let result = localeValue
|
||||
.replace(
|
||||
new RegExp(
|
||||
`(${CJK_LATIN_SPACED_TERM_PATTERN})([\\u3040-\\u30ff\\u3400-\\u9fff\\uac00-\\ud7af])`,
|
||||
'g'
|
||||
),
|
||||
'$1 $2'
|
||||
)
|
||||
.replace(
|
||||
new RegExp(
|
||||
`([\\u3040-\\u30ff\\u3400-\\u9fff\\uac00-\\ud7af])(${CJK_LATIN_SPACED_TERM_PATTERN})`,
|
||||
'g'
|
||||
),
|
||||
'$1 $2'
|
||||
)
|
||||
.replace(
|
||||
new RegExp(`(${CJK_LATIN_SPACED_TERM_PATTERN})(${CJK_LATIN_SPACED_TERM_PATTERN})`, 'g'),
|
||||
'$1 $2'
|
||||
)
|
||||
if (locale === 'ko') {
|
||||
result = result.replace(
|
||||
new RegExp(
|
||||
`(${CJK_LATIN_SPACED_TERM_PATTERN}) (가|이|은|는|을|를|와|과|의|로|으로|에서|에게|도|만|부터|까지)`,
|
||||
'g'
|
||||
),
|
||||
'$1$2'
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function applyPhraseFixes(enValue, localeValue, locale) {
|
||||
let result = localeValue
|
||||
for (const fix of LOCALE_PHRASE_FIXES[locale] ?? []) {
|
||||
|
|
@ -205,28 +366,33 @@ function applyPhraseFixes(enValue, localeValue, locale) {
|
|||
export function repairTranslatedValue({ key, enValue, localeValue, locale }) {
|
||||
const keyOverride = LOCALE_KEY_OVERRIDES[key]?.[locale]
|
||||
if (keyOverride) {
|
||||
return keyOverride
|
||||
// Why: exact key overrides can still carry stale MT output, so glossary repairs remain the final gate.
|
||||
let result = applyBrandMistranslationFixes(enValue, keyOverride, locale)
|
||||
result = applyPhraseFixes(enValue, result, locale)
|
||||
if (['zh', 'ja', 'ko'].includes(locale)) {
|
||||
result = applyCjkLatinTermSpacing(result, locale)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
if (shouldPreserveEnglishValue(enValue, key)) {
|
||||
return enValue
|
||||
}
|
||||
|
||||
const override = LOCALE_VALUE_OVERRIDES[locale]?.[enValue]
|
||||
if (override) {
|
||||
return override
|
||||
}
|
||||
let result = LOCALE_VALUE_OVERRIDES[locale]?.[enValue] ?? localeValue
|
||||
|
||||
if (key.includes('.search.')) {
|
||||
const searchOverride = SEARCH_KEYWORD_OVERRIDES[locale]?.[enValue]
|
||||
if (searchOverride) {
|
||||
return searchOverride
|
||||
result = searchOverride
|
||||
}
|
||||
}
|
||||
|
||||
let result = localeValue
|
||||
result = applyBrandMistranslationFixes(enValue, result, locale)
|
||||
result = applyPhraseFixes(enValue, result, locale)
|
||||
if (['zh', 'ja', 'ko'].includes(locale)) {
|
||||
result = applyCjkLatinTermSpacing(result, locale)
|
||||
}
|
||||
|
||||
if (enValue.includes('orca://')) {
|
||||
result = result.replace(/虎鲸:\/\//g, 'orca://')
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ describe('locale-translation-policy', () => {
|
|||
localeValue: '壊れた小切手に対して AI エージェントを開始しました。',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('失敗したチェックに対して AI エージェントを開始しました。')
|
||||
).toBe('失敗したチェックに対して AI agent を開始しました。')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.hooks.useSettingsNavigationMetadata.95a1886d94',
|
||||
|
|
@ -88,7 +88,7 @@ describe('locale-translation-policy', () => {
|
|||
localeValue: '電話機からターミナルとエージェントを制御します。',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('スマートフォンからターミナルとエージェントを操作')
|
||||
).toBe('スマートフォンから terminals と agents を操作')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.GitHubItemDialog.934add88b6',
|
||||
|
|
@ -267,7 +267,7 @@ describe('locale-translation-policy', () => {
|
|||
localeValue: '未已检测代理',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('未检测到代理')
|
||||
).toBe('未检测到 agents')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.skills.SkillsPage.38e0951c3a',
|
||||
|
|
@ -275,7 +275,7 @@ describe('locale-translation-policy', () => {
|
|||
localeValue: '代理技巧',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('代理技能')
|
||||
).toBe('Agent 技能')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.appearance.search.9ae151b26b',
|
||||
|
|
@ -358,7 +358,7 @@ describe('locale-translation-policy', () => {
|
|||
localeValue: 'コミットするものは何もありません。 PR はすでに統合されています。',
|
||||
locale: 'ja'
|
||||
})
|
||||
).toBe('コミットするものはありません。PR はすでにマージされています。')
|
||||
).toBe('commit するものはありません。PR はすでにマージされています。')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.integrations.search.581844769a',
|
||||
|
|
|
|||
|
|
@ -85,4 +85,155 @@ describe('locale-translation-policy zh round 5', () => {
|
|||
})
|
||||
).toBe('集成')
|
||||
})
|
||||
|
||||
it('keeps Terminal as a product surface term', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.Settings.3de4bbb841',
|
||||
enValue: 'Terminal',
|
||||
localeValue: '终端',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Terminal')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.feature.wall.BrowserAnimatedVisual.04096318ab',
|
||||
enValue: 'Terminal 1',
|
||||
localeValue: '终端 1',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Terminal 1')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.agent.AgentCombobox.986f946354',
|
||||
enValue: 'Blank Terminal',
|
||||
localeValue: '空白端子',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('空白 Terminal')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.terminal.pane.TerminalContextMenu.20e565d865',
|
||||
enValue: 'Split Terminal Right',
|
||||
localeValue: '分体式端子右',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('向右拆分 Terminal')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.TerminalAppearanceSection.abcb4dd019',
|
||||
enValue: 'Terminal Cursor',
|
||||
localeValue: '终端Cursor',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Terminal Cursor')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.terminal.FloatingTerminalPanel.3215fc73e9',
|
||||
enValue: 'New Terminal',
|
||||
localeValue: '新Terminal',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('新 Terminal')
|
||||
})
|
||||
|
||||
it('keeps workflow terms in English', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.sidebar.SidebarNav.9c95e1ce91',
|
||||
enValue: 'Agents',
|
||||
localeValue: '代理',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Agents')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.GitHubItemDialog.28986b3747',
|
||||
enValue: 'Started an AI agent for the broken checks.',
|
||||
localeValue: '已启动 AI 代理处理失败的检查。',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('已启动 AI agent 处理失败的检查。')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.LinearIssueMarkdownDescriptionEditor.d9c47069ef',
|
||||
enValue: 'Markdown',
|
||||
localeValue: '降价',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Markdown')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.TaskPage.7f3f7b4c18',
|
||||
enValue: 'Description (optional, markdown)',
|
||||
localeValue: '描述(可选,降价)',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('描述(可选,markdown)')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.sidebar.local.base.ref.suggestion.toast.commits',
|
||||
enValue: 'commits',
|
||||
localeValue: '次提交',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('commits')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.store.slices.worktrees.d1d78a7baa',
|
||||
enValue:
|
||||
'Git could not safely delete branch "{{value0}}"{{value1}}, so Orca kept it to avoid losing local commits.',
|
||||
localeValue:
|
||||
'Git 无法安全删除分支“{{value0}}”{{value1}},因此 Orca 保留它以避免丢失本地提交。',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Git 无法安全删除分支“{{value0}}”{{value1}},因此 Orca 保留它以避免丢失本地 commits。')
|
||||
})
|
||||
|
||||
it('does not confuse proxy copy with Agent terminology', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.GeneralNetworkSettingsSection.f6d76cc8f4',
|
||||
enValue: 'Proxy Bypass Rules',
|
||||
localeValue: 'Agent绕过规则',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('代理绕过规则')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.GeneralNetworkSettingsSection.1e214e265a',
|
||||
enValue:
|
||||
'Leave empty to use system proxy settings and inherited proxy environment variables.',
|
||||
localeValue: '留空以使用系统Agent设置和继承的Agent环境变量。',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('留空以使用系统代理设置和继承的代理环境变量。')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.general.search.91a46caafc',
|
||||
enValue: 'no_proxy',
|
||||
localeValue: '无Agent',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('no_proxy')
|
||||
})
|
||||
|
||||
it('keeps repo terminology in English', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.0b1766738a',
|
||||
enValue: 'Repo',
|
||||
localeValue: '回购协议',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Repo')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.sidebar.add.repo.local.start.actions.fb4fc5380e',
|
||||
enValue: 'Local project, Git repo, or folder with many repos',
|
||||
localeValue: '本地项目、Git 存储库或包含多个存储库的文件夹',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('本地项目、Git repo 或包含多个 repos 的文件夹')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ export const LOCALE_VALUE_OVERRIDES = {
|
|||
es: {
|
||||
'Explore Orca': 'Explorar Orca',
|
||||
'OpenCode Go': 'OpenCode Go',
|
||||
'Open in Cursor': 'Abrir en Cursor'
|
||||
'Open in Cursor': 'Abrir en Cursor',
|
||||
'Local project, Git repo, or folder with many repos':
|
||||
'Proyecto local, repo de Git o carpeta con muchos repos'
|
||||
},
|
||||
ko: {
|
||||
Save: '저장',
|
||||
|
|
|
|||
|
|
@ -156,6 +156,17 @@ export const ZH_VALUE_OVERRIDES = {
|
|||
'自您上次批准以来已发生变化。运行前请重新评审',
|
||||
'Run the weekly dependency audit and summarize risky changes.':
|
||||
'每周运行依赖项审计并总结有风险的更改。',
|
||||
'This turns on a process-wide Electron networking switch after restart. Use it for corporate VPNs or proxies that reject HTTP/2 update downloads.':
|
||||
'这将在重启后启用进程级 Electron 网络开关。适用于拒绝 HTTP/2 更新下载的企业 VPN 或代理。',
|
||||
'Use only when a corporate VPN or proxy breaks update downloads with HTTP/2 protocol errors. It affects all Electron networking after restart.':
|
||||
'仅当企业 VPN 或代理因 HTTP/2 协议错误而中断更新下载时使用。重启后会影响所有 Electron 网络。',
|
||||
'Use HTTP/1.1 for Electron networking when HTTP/2 fails behind a proxy.':
|
||||
'当 HTTP/2 在代理后面失败时,使用 HTTP/1.1 进行 Electron 网络。',
|
||||
'Proxy Bypass Rules': '代理绕过规则',
|
||||
'Hosts that should bypass the configured HTTP proxy.': '应绕过配置的 HTTP 代理的主机。',
|
||||
'Leave empty to use system proxy settings and inherited proxy environment variables.':
|
||||
'留空以使用系统代理设置和继承的代理环境变量。',
|
||||
'Proxy Command': '代理命令',
|
||||
"Give agents direct access to Orca's browser so they can test pages, capture screenshots, and act on what they see.":
|
||||
'让代理直接访问 Orca 的浏览器,以便测试页面、捕获屏幕截图并根据所见内容执行操作。',
|
||||
'X finishes, send it the review task.”': 'X 完成后,把评审任务发给它。”',
|
||||
|
|
@ -176,6 +187,17 @@ export const ZH_VALUE_OVERRIDES = {
|
|||
'每个已连接的 Linear 工作区都有一个由活动运行时存储的密钥。全权限密钥可覆盖密钥所有者可访问的所有团队;受限密钥可随时更换。',
|
||||
'Show Linear in the Tasks source picker and sidebar shortcuts.':
|
||||
'在任务源选择器和侧边栏快捷方式中显示 Linear。',
|
||||
'Local project, Git repo, or folder with many repos':
|
||||
'本地项目、Git repo 或包含多个 repos 的文件夹',
|
||||
'Staged Changes': '已暂存的更改',
|
||||
Changes: '更改',
|
||||
'Untracked Files': '未跟踪文件',
|
||||
'Split Up': '向上拆分',
|
||||
'Split Down': '向下拆分',
|
||||
'Split Left': '向左拆分',
|
||||
'Split Right': '向右拆分',
|
||||
'Split Terminal Down': '向下拆分 Terminal',
|
||||
'Split Terminal Right': '向右拆分 Terminal',
|
||||
'Optional account switching for Claude while preserving shared chat context.':
|
||||
'Claude 的可选账户切换,同时保留共享聊天上下文。',
|
||||
'Countdown timer showing time until prompt cache expires (Claude agents).':
|
||||
|
|
|
|||
|
|
@ -262,10 +262,19 @@ const PRIMARY_ICONS: Partial<
|
|||
// This keeps unresolved conflicts visible at the top of the list where the
|
||||
// user won't miss them.
|
||||
const SECTION_ORDER = ['unstaged', 'staged', 'untracked'] as const
|
||||
const SECTION_LABELS: Record<(typeof SECTION_ORDER)[number], string> = {
|
||||
staged: 'Staged Changes',
|
||||
unstaged: 'Changes',
|
||||
untracked: 'Untracked Files'
|
||||
const SECTION_LABELS: Record<(typeof SECTION_ORDER)[number], { key: string; fallback: string }> = {
|
||||
staged: {
|
||||
key: 'auto.components.right.sidebar.SourceControl.48a003c1b1',
|
||||
fallback: 'Staged Changes'
|
||||
},
|
||||
unstaged: {
|
||||
key: 'auto.components.right.sidebar.SourceControl.d4ef4bafc5',
|
||||
fallback: 'Changes'
|
||||
},
|
||||
untracked: {
|
||||
key: 'auto.components.right.sidebar.SourceControl.522f44dce5',
|
||||
fallback: 'Untracked Files'
|
||||
}
|
||||
}
|
||||
|
||||
const BRANCH_REFRESH_INTERVAL_MS = 5000
|
||||
|
|
@ -4212,10 +4221,11 @@ function SourceControlInner(): React.JSX.Element {
|
|||
getUnstageAllPaths(grouped.staged).length > 0
|
||||
const canRevertAll =
|
||||
!normalizedFilter && getDiscardAllPaths(grouped[area], area).length > 0
|
||||
const sectionLabel = SECTION_LABELS[area]
|
||||
return (
|
||||
<div key={area}>
|
||||
<SectionHeader
|
||||
label={SECTION_LABELS[area]}
|
||||
label={translate(sectionLabel.key, sectionLabel.fallback)}
|
||||
count={items.length}
|
||||
conflictCount={
|
||||
items.filter((entry) => entry.conflictStatus === 'unresolved').length
|
||||
|
|
|
|||
|
|
@ -7740,6 +7740,9 @@
|
|||
"7a09d7f9d2": "base",
|
||||
"383cf92c73": "tree",
|
||||
"d7ae61269b": "Committed on Branch",
|
||||
"48a003c1b1": "Staged Changes",
|
||||
"d4ef4bafc5": "Changes",
|
||||
"522f44dce5": "Untracked Files",
|
||||
"3636d0f686": "ready",
|
||||
"d2e9189866": "all",
|
||||
"a0cc0e6b4e": "loading",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue