Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/languages/lib/correct-translation-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ export function correctTranslatedContentStrings(
// `{% icono "X" ... %}` — "icono" = "icon" = octicon
content = content.replaceAll('{% icono ', '{% octicon ')
content = content.replaceAll('{%- icono ', '{%- octicon ')
// `{% alto "X" ... %}` — "alto" used as alias for octicon (observed in billing reusable)
content = content.replaceAll('{% alto ', '{% octicon ')
content = content.replaceAll('{%- alto ', '{%- octicon ')
// `{% octicon "bombilla" %}` — Spanish "bombilla" = "light-bulb" (translated octicon name)
content = content.replaceAll('{% octicon "bombilla"', '{% octicon "light-bulb"')
content = content.replaceAll('{%- octicon "bombilla"', '{%- octicon "light-bulb"')
Expand Down Expand Up @@ -643,6 +646,12 @@ export function correctTranslatedContentStrings(
/\{%(-?)\s*(fpt|ghec|ghes)\s+ifversion\s*%\}/g,
'{%$1 ifversion $2 %}',
)
// Multi-plan word-order swap: `{% ghes ifversion ou ghec %}` → `{% ifversion ghes or ghec %}`
// Handles the combination of word-order inversion AND Portuguese "ou" for "or".
content = content.replace(
/\{%(-?)\s*(fpt|ghec|ghes|ghae)\s+ifversion\s+(?:ou|or)\s+(fpt|ghec|ghes|ghae)\s*(-?)%\}/g,
'{%$1 ifversion $2 or $3 $4%}',
)
// With extra "de" word: `{% ghes de ifversion %}` → `{% ifversion ghes %}`
content = content.replace(
/\{%(-?)\s*(fpt|ghec|ghes)\s+de\s+ifversion\s*%\}/g,
Expand Down Expand Up @@ -1329,6 +1338,30 @@ export function correctTranslatedContentStrings(
/\{%(-?)\s*des(?:\s+[^{}%\n]+?)?\s+variables\.([A-Za-z0-9._-]+)(\s*-?%\})/g,
'{%$1 data variables.$2$3',
)
// `{% modules réutilisables.X %}` — French "modules réutilisables" = "reusable modules"
// used in place of `{% data reusables.X %}`.
content = content.replaceAll('{% modules réutilisables.', '{% data reusables.')
content = content.replaceAll('{%- modules réutilisables.', '{%- data reusables.')
// `{% flux de travail variables.X %}` — French "flux de travail" = "workflow" was
// mistakenly substituted for the "data" keyword in data variable references.
content = content.replaceAll('{% flux de travail variables.', '{% data variables.')
content = content.replaceAll('{%- flux de travail variables.', '{%- data variables.')
// `{% invite %}` / `{%- invite %}` — French "invite" = "prompt"; translator used the
// French word as the tag opener for the `{% prompt %}` block tag.
content = content.replaceAll('{% invite %}', '{% prompt %}')
content = content.replaceAll('{%- invite %}', '{%- prompt %}')
content = content.replaceAll('{% invite -%}', '{% prompt -%}')
content = content.replaceAll('{%- invite -%}', '{%- prompt -%}')
// `{% collaborateurs invités ifversion %}` — French translation of
// `{% ifversion guest-collaborators %}` with both word-order swap and full translation.
content = content.replaceAll(
'{% collaborateurs invités ifversion %}',
'{% ifversion guest-collaborators %}',
)
content = content.replaceAll(
'{%- collaborateurs invités ifversion %}',
'{%- ifversion guest-collaborators %}',
)
}

if (context.code === 'ko') {
Expand Down Expand Up @@ -1678,6 +1711,15 @@ export function correctTranslatedContentStrings(

// --- Generic fixes (all languages) ---

// [copilot/tutorials/learn-a-new-language] The `${numCats}` JS template literal inside
// a backtick code span confused translators and caused the closing `{% endprompt %}` to
// be dropped from the JavaScript-conditional-example prompt block. Fix by appending
// `{% endprompt %}` to the line that contains the distinctive code.
content = content.replace(
/(\* \{%[- ]prompt [-]?%\}(?![^\n]*\{%-?\s*endprompt\s*-?%\})[^\n]*'cat is' : 'cats are'\} hungry\.[^\n]*(?:\?|?)[^\n]*)(\n|$)/g,
'$1{% endprompt %}$2',
)

// Inside ANY Liquid tag `{% ... %}` (including `{% octicon ... %}`,
// `{% data ... %}`, `{% assign ... %}` etc.), normalize typographic
// quotation marks back to ASCII straight quotes. Translators
Expand Down
68 changes: 68 additions & 0 deletions src/languages/tests/correct-translation-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ describe('correctTranslatedContentStrings', () => {
expect(fix('{%- icono "check" %}', 'es')).toBe('{%- octicon "check" %}')
})

test('fixes alto → octicon', () => {
expect(fix('{% alto "link-external":16 aria-label="link-external" %}', 'es')).toBe(
'{% octicon "link-external":16 aria-label="link-external" %}',
)
expect(fix('{%- alto "check" %}', 'es')).toBe('{%- octicon "check" %}')
})

test('fixes octicon "bombilla" → octicon "light-bulb"', () => {
expect(fix('{% octicon "bombilla" aria-label="The light-bulb icon" %}', 'es')).toBe(
'{% octicon "light-bulb" aria-label="The light-bulb icon" %}',
Expand Down Expand Up @@ -424,6 +431,14 @@ describe('correctTranslatedContentStrings', () => {
expect(fix('{% if condition ou other %}', 'pt')).toBe('{% if condition or other %}')
})

test('fixes multi-plan word-order swap with ou (ghes ifversion ou ghec)', () => {
// `{% ghes ifversion ou ghec %}` — word-order swap + Portuguese "ou" for "or"
expect(fix('{% ghes ifversion ou ghec %}', 'pt')).toBe('{% ifversion ghes or ghec %}')
expect(fix('{%- ghes ifversion ou ghec %}', 'pt')).toBe('{%- ifversion ghes or ghec %}')
expect(fix('{% fpt ifversion ou ghec %}', 'pt')).toBe('{% ifversion fpt or ghec %}')
expect(fix('{% ghec ifversion ou ghes %}', 'pt')).toBe('{% ifversion ghec or ghes %}')
})

test('fixes fully translated reutilizáveis reusables path', () => {
// `reutilizáveis` is Portuguese for "reusables"
expect(fix('{% dados reutilizáveis.repositórios.reaction_list %}', 'pt')).toBe(
Expand Down Expand Up @@ -956,6 +971,41 @@ describe('correctTranslatedContentStrings', () => {
fix('{% données réutilisables propriétés-personnalisées valeurs-requises %}', 'fr'),
).toBe('{% data reusables.organizations.custom-properties-required-values %}')
})

test('fixes modules réutilisables → data reusables', () => {
expect(fix('{% modules réutilisables.enterprise_migrations.ready-to-import %}', 'fr')).toBe(
'{% data reusables.enterprise_migrations.ready-to-import %}',
)
expect(fix('{%- modules réutilisables.foo.bar %}', 'fr')).toBe(
'{%- data reusables.foo.bar %}',
)
})

test('fixes flux de travail variables → data variables', () => {
// `{% flux de travail variables.` — French "flux de travail" (workflow) mistakenly
// used as the Liquid tag name instead of "data".
expect(fix('{% flux de travail variables.product.prodname_actions %}', 'fr')).toBe(
'{% data variables.product.prodname_actions %}',
)
expect(fix('{%- flux de travail variables.copilot.foo %}', 'fr')).toBe(
'{%- data variables.copilot.foo %}',
)
})

test('fixes invite → prompt', () => {
expect(fix('{% invite %}', 'fr')).toBe('{% prompt %}')
expect(fix('{%- invite %}', 'fr')).toBe('{%- prompt %}')
expect(fix('{% invite -%}', 'fr')).toBe('{% prompt -%}')
})

test('fixes collaborateurs invités ifversion → ifversion guest-collaborators', () => {
expect(fix('{% collaborateurs invités ifversion %}', 'fr')).toBe(
'{% ifversion guest-collaborators %}',
)
expect(fix('{%- collaborateurs invités ifversion %}', 'fr')).toBe(
'{%- ifversion guest-collaborators %}',
)
})
})

// ─── KOREAN (ko) ──────────────────────────────────────────────────
Expand Down Expand Up @@ -1606,6 +1656,24 @@ describe('correctTranslatedContentStrings', () => {
)
})

test('fixes missing endprompt on the JS-numCats line (all translation languages)', () => {
// The `${}` template literal inside a backtick confused translators and they dropped
// `{% endprompt %}` from the line. Fix is applied universally across all languages.
const input =
"* {% prompt %}How do I write `The ${'cat is' : 'cats are'} hungry.`?{% endprompt %}\n" +
"* {% prompt %}In JS I'd write: `The ${'cat is' : 'cats are'} hungry.`. ¿How in NEW-LANGUAGE?\n" +
'* {% prompt %}Next question?{% endprompt %}'
const output =
"* {% prompt %}How do I write `The ${'cat is' : 'cats are'} hungry.`?{% endprompt %}\n" +
"* {% prompt %}In JS I'd write: `The ${'cat is' : 'cats are'} hungry.`. ¿How in NEW-LANGUAGE?{% endprompt %}\n" +
'* {% prompt %}Next question?{% endprompt %}'
expect(fix(input, 'es')).toBe(output)
expect(fix(input, 'pt')).toBe(output)
expect(fix(input, 'zh')).toBe(output)
expect(fix(input, 'de')).toBe(output)
expect(fix(input, 'fr')).toBe(output)
})

test('recovers linebreaks from English', () => {
const en = '{% endif %}\nSome text'
const translated = '{% endif %} Some text'
Expand Down
Loading