fix(i18n): correct Spanish "PR" mistranslated as "relaciones públicas" (#6749)

Machine translation rendered the abbreviation "PR" (pull request) as "relaciones públicas" (public relations) across 32 Spanish UI strings — e.g. "Reabrir relaciones públicas" (Reopen PR), "Crear relaciones públicas" (Create PR), "Abrir cheques de relaciones públicas" (Open PR checks).

Adds the missing `es` rule to LOCALE_PHRASE_FIXES. Because "relaciones públicas" is a real Spanish phrase (unlike the CJK-only ko/zh patterns 홍보/公关), the rule guards on the actual `PR`/`PRs` token via a new optional `whenEnMatches` RegExp guard in applyPhraseFixes, so it never fires on unrelated English that merely contains a "pr" substring (approve, preview, press). `whenEnMatches` is backward compatible — existing `whenEnIncludes` rules are unchanged.

Regenerates the 32 affected es.json leaves. Adds a vitest spec covering the rewrites and a negative case proving the token guard ignores genuine "public relations" strings.
This commit is contained in:
Miguel Echávarri 2026-06-29 20:56:26 +02:00 committed by GitHub
parent 630c1b45d7
commit cd03928e94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 106 additions and 34 deletions

View File

@ -378,5 +378,13 @@ export const LOCALE_PHRASE_FIXES = {
{ pattern: /注解/g, replacement: '批注', whenEnIncludes: 'Annotation' },
...ZH_PHRASE_FIXES_ROUND5
],
ja: JA_PHRASE_FIXES
ja: JA_PHRASE_FIXES,
es: [
// Why: machine translation renders the abbreviation "PR" (pull request) as
// "relaciones públicas" (public relations). Unlike the ko/zh glossary fixes whose
// patterns are CJK-only, "relaciones públicas" is a real Spanish phrase, so the guard
// matches the actual `PR`/`PRs` token (not a loose "pr" substring) to avoid rewriting
// English that is genuinely about public relations.
{ pattern: /relaciones públicas/g, replacement: 'PR', whenEnMatches: /\bPRs?\b/ }
]
}

View File

@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest'
import { repairTranslatedValue } from './locale-translation-policy.mjs'
const repairEs = (enValue, localeValue) =>
repairTranslatedValue({
key: 'auto.components.test.pr-glossary',
enValue,
localeValue,
locale: 'es'
})
describe('locale-translation-policy es PR glossary', () => {
it('rewrites the "relaciones públicas" mistranslation of PR back to PR', () => {
expect(repairEs('PR', 'relaciones públicas')).toBe('PR')
expect(repairEs('PRs', 'relaciones públicas')).toBe('PR')
expect(repairEs('Reopen PR', 'Reabrir relaciones públicas')).toBe('Reabrir PR')
expect(repairEs('Create PR', 'Crear relaciones públicas')).toBe('Crear PR')
expect(repairEs('Open PR checks', 'Abrir cheques de relaciones públicas')).toBe(
'Abrir cheques de PR'
)
expect(repairEs('unlink PR', 'desvincular relaciones públicas')).toBe('desvincular PR')
})
it('rewrites PR inside longer sentences', () => {
expect(
repairEs(
'Add Orca attribution to commits, PRs, and issues.',
'Agregue la atribución de Orca a commits, relaciones públicas y problemas.'
)
).toBe('Agregue la atribución de Orca a commits, PR y problemas.')
expect(
repairEs(
'Open the PR details to view current reviewers.',
'Abra los detalles de relaciones públicas para ver los revisores actuales.'
)
).toBe('Abra los detalles de PR para ver los revisores actuales.')
})
it('leaves an already correct PR translation untouched', () => {
expect(repairEs('Reopen PR', 'Reabrir PR')).toBe('Reabrir PR')
})
it('does not fire when the English has a "pr" substring but no PR token', () => {
// "approve"/"public" contain the substring "pr" but not the PR token, so a genuine
// "public relations" string must be left alone.
expect(repairEs('Approve public relations', 'Aprobar relaciones públicas')).toBe(
'Aprobar relaciones públicas'
)
expect(repairEs('Compress the preview', 'Comprimir las relaciones públicas')).toBe(
'Comprimir las relaciones públicas'
)
})
})

View File

@ -435,10 +435,20 @@ function applyCjkLatinTermSpacing(localeValue, locale) {
return result
}
function phraseFixMatchesEnglish(enValue, fix) {
// Why: `whenEnMatches` (a RegExp) lets a rule guard on a real token (e.g. /\bPRs?\b/)
// instead of the looser case-insensitive `whenEnIncludes` substring, so a phrase fix can
// avoid firing on unrelated English that merely contains the substring (approve, preview).
if (fix.whenEnMatches) {
return fix.whenEnMatches.test(enValue)
}
return enValue.toLowerCase().includes(fix.whenEnIncludes.toLowerCase())
}
function applyPhraseFixes(enValue, localeValue, locale) {
let result = localeValue
for (const fix of LOCALE_PHRASE_FIXES[locale] ?? []) {
if (!enValue.toLowerCase().includes(fix.whenEnIncludes.toLowerCase())) {
if (!phraseFixMatchesEnglish(enValue, fix)) {
continue
}
result = result.replace(fix.pattern, fix.replacement)

File diff suppressed because one or more lines are too long