Skip to content

Commit 089f85f

Browse files
docs-botCopilotCopilotheiskr
authored
fix(translations): add new Liquid corruption fixes for fr/es/pt + generic endprompt (#61615)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: heiskr <1221423+heiskr@users.noreply.github.com> Co-authored-by: Kevin Heis <heiskr@users.noreply.github.com>
1 parent 4a5568d commit 089f85f

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

src/languages/lib/correct-translation-content.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ export function correctTranslatedContentStrings(
177177
// `{% icono "X" ... %}` — "icono" = "icon" = octicon
178178
content = content.replaceAll('{% icono ', '{% octicon ')
179179
content = content.replaceAll('{%- icono ', '{%- octicon ')
180+
// `{% alto "X" ... %}` — "alto" used as alias for octicon (observed in billing reusable)
181+
content = content.replaceAll('{% alto ', '{% octicon ')
182+
content = content.replaceAll('{%- alto ', '{%- octicon ')
180183
// `{% octicon "bombilla" %}` — Spanish "bombilla" = "light-bulb" (translated octicon name)
181184
content = content.replaceAll('{% octicon "bombilla"', '{% octicon "light-bulb"')
182185
content = content.replaceAll('{%- octicon "bombilla"', '{%- octicon "light-bulb"')
@@ -643,6 +646,12 @@ export function correctTranslatedContentStrings(
643646
/\{%(-?)\s*(fpt|ghec|ghes)\s+ifversion\s*%\}/g,
644647
'{%$1 ifversion $2 %}',
645648
)
649+
// Multi-plan word-order swap: `{% ghes ifversion ou ghec %}` → `{% ifversion ghes or ghec %}`
650+
// Handles the combination of word-order inversion AND Portuguese "ou" for "or".
651+
content = content.replace(
652+
/\{%(-?)\s*(fpt|ghec|ghes|ghae)\s+ifversion\s+(?:ou|or)\s+(fpt|ghec|ghes|ghae)\s*(-?)%\}/g,
653+
'{%$1 ifversion $2 or $3 $4%}',
654+
)
646655
// With extra "de" word: `{% ghes de ifversion %}` → `{% ifversion ghes %}`
647656
content = content.replace(
648657
/\{%(-?)\s*(fpt|ghec|ghes)\s+de\s+ifversion\s*%\}/g,
@@ -1329,6 +1338,30 @@ export function correctTranslatedContentStrings(
13291338
/\{%(-?)\s*des(?:\s+[^{}%\n]+?)?\s+variables\.([A-Za-z0-9._-]+)(\s*-?%\})/g,
13301339
'{%$1 data variables.$2$3',
13311340
)
1341+
// `{% modules réutilisables.X %}` — French "modules réutilisables" = "reusable modules"
1342+
// used in place of `{% data reusables.X %}`.
1343+
content = content.replaceAll('{% modules réutilisables.', '{% data reusables.')
1344+
content = content.replaceAll('{%- modules réutilisables.', '{%- data reusables.')
1345+
// `{% flux de travail variables.X %}` — French "flux de travail" = "workflow" was
1346+
// mistakenly substituted for the "data" keyword in data variable references.
1347+
content = content.replaceAll('{% flux de travail variables.', '{% data variables.')
1348+
content = content.replaceAll('{%- flux de travail variables.', '{%- data variables.')
1349+
// `{% invite %}` / `{%- invite %}` — French "invite" = "prompt"; translator used the
1350+
// French word as the tag opener for the `{% prompt %}` block tag.
1351+
content = content.replaceAll('{% invite %}', '{% prompt %}')
1352+
content = content.replaceAll('{%- invite %}', '{%- prompt %}')
1353+
content = content.replaceAll('{% invite -%}', '{% prompt -%}')
1354+
content = content.replaceAll('{%- invite -%}', '{%- prompt -%}')
1355+
// `{% collaborateurs invités ifversion %}` — French translation of
1356+
// `{% ifversion guest-collaborators %}` with both word-order swap and full translation.
1357+
content = content.replaceAll(
1358+
'{% collaborateurs invités ifversion %}',
1359+
'{% ifversion guest-collaborators %}',
1360+
)
1361+
content = content.replaceAll(
1362+
'{%- collaborateurs invités ifversion %}',
1363+
'{%- ifversion guest-collaborators %}',
1364+
)
13321365
}
13331366

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

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

1714+
// [copilot/tutorials/learn-a-new-language] The `${numCats}` JS template literal inside
1715+
// a backtick code span confused translators and caused the closing `{% endprompt %}` to
1716+
// be dropped from the JavaScript-conditional-example prompt block. Fix by appending
1717+
// `{% endprompt %}` to the line that contains the distinctive code.
1718+
content = content.replace(
1719+
/(\* \{%[- ]prompt [-]?%\}(?![^\n]*\{%-?\s*endprompt\s*-?%\})[^\n]*'cat is' : 'cats are'\} hungry\.[^\n]*(?:\?|)[^\n]*)(\n|$)/g,
1720+
'$1{% endprompt %}$2',
1721+
)
1722+
16811723
// Inside ANY Liquid tag `{% ... %}` (including `{% octicon ... %}`,
16821724
// `{% data ... %}`, `{% assign ... %}` etc.), normalize typographic
16831725
// quotation marks back to ASCII straight quotes. Translators

src/languages/tests/correct-translation-content.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ describe('correctTranslatedContentStrings', () => {
124124
expect(fix('{%- icono "check" %}', 'es')).toBe('{%- octicon "check" %}')
125125
})
126126

127+
test('fixes alto → octicon', () => {
128+
expect(fix('{% alto "link-external":16 aria-label="link-external" %}', 'es')).toBe(
129+
'{% octicon "link-external":16 aria-label="link-external" %}',
130+
)
131+
expect(fix('{%- alto "check" %}', 'es')).toBe('{%- octicon "check" %}')
132+
})
133+
127134
test('fixes octicon "bombilla" → octicon "light-bulb"', () => {
128135
expect(fix('{% octicon "bombilla" aria-label="The light-bulb icon" %}', 'es')).toBe(
129136
'{% octicon "light-bulb" aria-label="The light-bulb icon" %}',
@@ -424,6 +431,14 @@ describe('correctTranslatedContentStrings', () => {
424431
expect(fix('{% if condition ou other %}', 'pt')).toBe('{% if condition or other %}')
425432
})
426433

434+
test('fixes multi-plan word-order swap with ou (ghes ifversion ou ghec)', () => {
435+
// `{% ghes ifversion ou ghec %}` — word-order swap + Portuguese "ou" for "or"
436+
expect(fix('{% ghes ifversion ou ghec %}', 'pt')).toBe('{% ifversion ghes or ghec %}')
437+
expect(fix('{%- ghes ifversion ou ghec %}', 'pt')).toBe('{%- ifversion ghes or ghec %}')
438+
expect(fix('{% fpt ifversion ou ghec %}', 'pt')).toBe('{% ifversion fpt or ghec %}')
439+
expect(fix('{% ghec ifversion ou ghes %}', 'pt')).toBe('{% ifversion ghec or ghes %}')
440+
})
441+
427442
test('fixes fully translated reutilizáveis reusables path', () => {
428443
// `reutilizáveis` is Portuguese for "reusables"
429444
expect(fix('{% dados reutilizáveis.repositórios.reaction_list %}', 'pt')).toBe(
@@ -956,6 +971,41 @@ describe('correctTranslatedContentStrings', () => {
956971
fix('{% données réutilisables propriétés-personnalisées valeurs-requises %}', 'fr'),
957972
).toBe('{% data reusables.organizations.custom-properties-required-values %}')
958973
})
974+
975+
test('fixes modules réutilisables → data reusables', () => {
976+
expect(fix('{% modules réutilisables.enterprise_migrations.ready-to-import %}', 'fr')).toBe(
977+
'{% data reusables.enterprise_migrations.ready-to-import %}',
978+
)
979+
expect(fix('{%- modules réutilisables.foo.bar %}', 'fr')).toBe(
980+
'{%- data reusables.foo.bar %}',
981+
)
982+
})
983+
984+
test('fixes flux de travail variables → data variables', () => {
985+
// `{% flux de travail variables.` — French "flux de travail" (workflow) mistakenly
986+
// used as the Liquid tag name instead of "data".
987+
expect(fix('{% flux de travail variables.product.prodname_actions %}', 'fr')).toBe(
988+
'{% data variables.product.prodname_actions %}',
989+
)
990+
expect(fix('{%- flux de travail variables.copilot.foo %}', 'fr')).toBe(
991+
'{%- data variables.copilot.foo %}',
992+
)
993+
})
994+
995+
test('fixes invite → prompt', () => {
996+
expect(fix('{% invite %}', 'fr')).toBe('{% prompt %}')
997+
expect(fix('{%- invite %}', 'fr')).toBe('{%- prompt %}')
998+
expect(fix('{% invite -%}', 'fr')).toBe('{% prompt -%}')
999+
})
1000+
1001+
test('fixes collaborateurs invités ifversion → ifversion guest-collaborators', () => {
1002+
expect(fix('{% collaborateurs invités ifversion %}', 'fr')).toBe(
1003+
'{% ifversion guest-collaborators %}',
1004+
)
1005+
expect(fix('{%- collaborateurs invités ifversion %}', 'fr')).toBe(
1006+
'{%- ifversion guest-collaborators %}',
1007+
)
1008+
})
9591009
})
9601010

9611011
// ─── KOREAN (ko) ──────────────────────────────────────────────────
@@ -1606,6 +1656,24 @@ describe('correctTranslatedContentStrings', () => {
16061656
)
16071657
})
16081658

1659+
test('fixes missing endprompt on the JS-numCats line (all translation languages)', () => {
1660+
// The `${}` template literal inside a backtick confused translators and they dropped
1661+
// `{% endprompt %}` from the line. Fix is applied universally across all languages.
1662+
const input =
1663+
"* {% prompt %}How do I write `The ${'cat is' : 'cats are'} hungry.`?{% endprompt %}\n" +
1664+
"* {% prompt %}In JS I'd write: `The ${'cat is' : 'cats are'} hungry.`. ¿How in NEW-LANGUAGE?\n" +
1665+
'* {% prompt %}Next question?{% endprompt %}'
1666+
const output =
1667+
"* {% prompt %}How do I write `The ${'cat is' : 'cats are'} hungry.`?{% endprompt %}\n" +
1668+
"* {% prompt %}In JS I'd write: `The ${'cat is' : 'cats are'} hungry.`. ¿How in NEW-LANGUAGE?{% endprompt %}\n" +
1669+
'* {% prompt %}Next question?{% endprompt %}'
1670+
expect(fix(input, 'es')).toBe(output)
1671+
expect(fix(input, 'pt')).toBe(output)
1672+
expect(fix(input, 'zh')).toBe(output)
1673+
expect(fix(input, 'de')).toBe(output)
1674+
expect(fix(input, 'fr')).toBe(output)
1675+
})
1676+
16091677
test('recovers linebreaks from English', () => {
16101678
const en = '{% endif %}\nSome text'
16111679
const translated = '{% endif %} Some text'

0 commit comments

Comments
 (0)