diff --git a/README.md b/README.md index 8fbb65e..80c64dc 100644 --- a/README.md +++ b/README.md @@ -120,12 +120,10 @@ python /scripts/localization.py --operation import \ The importer derives identity/runtime metadata from the extension manifest, requires all 14 configured locales, and validates every table before copying. -Each required real-language manifest/runtime table must contain translation -evidence: a nonempty target differing from English, or a target explicitly marked -translated/final/signed-off. Individual untranslated strings are allowed; -pseudo-localization is checked for coverage but is not real-language evidence. -Missing, empty, incomplete, or wholly untranslated handbacks fail explicitly. -Do not publish while waiting for service onboarding or real translated output. +Untranslated strings and wholly untranslated tables are allowed: users see the +English text until translated handbacks arrive. Translation state does not gate +release packaging. Missing, empty, malformed, or incomplete handbacks still fail +explicitly, including missing runtime tables when runtime localization is declared. Generated root `package.nls..json` and declared runtime locale files should be ignored by Git but included by VSCE. Keep English `package.nls.json` tracked. diff --git a/scripts/localization.py b/scripts/localization.py index 1a3fb30..fde779c 100644 --- a/scripts/localization.py +++ b/scripts/localization.py @@ -30,7 +30,6 @@ "zh-tw", ) LOCALE_ALIASES = {"zh-hans": "zh-cn", "zh-hant": "zh-tw"} -TRANSLATED_STATES = {"translated", "final", "signed-off"} def inside(root: pathlib.Path, value: str) -> pathlib.Path: @@ -115,9 +114,9 @@ def export_english( def validate_handback(files: list[pathlib.Path], runtime: bool) -> None: - """Require coverage and translation evidence in each required locale table.""" + """Require complete, nonempty tables while allowing untranslated strings.""" kinds = ("package", "bundle") if runtime else ("package",) - tables = {} + tables = set() for path in files: document = ET.parse(path) for table in document.findall(".//{*}file"): @@ -132,31 +131,17 @@ def validate_handback(files: list[pathlib.Path], runtime: bool) -> None: units = table.findall(".//{*}trans-unit") if not units: raise ValueError(f"Empty translation table: {locale}/{kind}") - translated = False for unit in units: source = unit.find("{*}source") - target = unit.find("{*}target") if source is None or not "".join(source.itertext()).strip(): raise ValueError(f"Missing English source in {locale}/{kind}") - if target is not None: - text = "".join(target.itertext()).strip() - translated |= bool(text) and ( - text != "".join(source.itertext()).strip() - or target.get("state") in TRANSLATED_STATES - ) - tables[key] = translated + tables.add(key) for locale in LOCALES: for kind in kinds: if (locale, kind) not in tables: raise ValueError( f"Incomplete translation handback: missing {locale}/{kind}" ) - if locale != "qps-ploc" and not tables[locale, kind]: - raise ValueError( - f"Translation handback is not ready: {locale}/{kind} is wholly " - "untranslated. Wait for translated output from the " - "localization service." - ) def import_translations( diff --git a/scripts/localization_test.py b/scripts/localization_test.py index 97adc45..a81980f 100644 --- a/scripts/localization_test.py +++ b/scripts/localization_test.py @@ -16,7 +16,7 @@ class LocalizationTests(unittest.TestCase): - """Exercise generic exports and fully staged, translation-ready imports.""" + """Exercise generic exports and fully staged imports with English fallback.""" def setUp(self): self.temporary = tempfile.TemporaryDirectory(prefix="shared localization ") @@ -176,19 +176,13 @@ def test_manifest_only_import(self): self.assertEqual(len(list(self.root.glob("package.nls.*.json"))), 14) self.assertFalse((self.root / "l10n").exists()) - def test_readiness_checked_per_table(self): + def test_wholly_untranslated_table_is_allowed(self): for kind in ("package", "bundle"): with self.subTest(kind=kind): self.handbacks(untranslated=("de", kind)) - existing = self.root / "package.nls.de.json" - existing.write_text('{"description":"keep"}', encoding="utf-8") - with self.assertRaisesRegex(ValueError, f"de/{kind} is wholly"): - localization.import_translations( - self.root, self.translations, self.output - ) - self.assertEqual(existing.read_text(), '{"description":"keep"}') - self.assertFalse(self.output.exists()) - self.calls.assert_not_called() + localization.validate_handback( + sorted(self.translations.rglob("*.xlf")), runtime=True + ) def test_partial_translation_is_allowed(self): files = self.handbacks() @@ -200,12 +194,13 @@ def test_source_identical_marked_translated_is_allowed(self): files = self.handbacks(same=True, state=state) localization.validate_handback(files, runtime=True) - def test_all_new_english_is_rejected(self): - files = self.handbacks(same=True) - with self.assertRaisesRegex(ValueError, "wholly untranslated"): - localization.validate_handback(files, runtime=True) + def test_all_new_english_is_allowed(self): + self.handbacks(same=True) + localization.import_translations(self.root, self.translations, self.output) + self.assertEqual(len(list(self.root.glob("package.nls.*.json"))), 14) + self.assertEqual(len(list((self.root / "l10n").glob("*.json"))), 14) - def test_pseudo_localization_is_not_translation_evidence(self): + def test_untranslated_pseudo_localization_is_allowed(self): files = self.handbacks(untranslated=("qps-ploc", "package")) localization.validate_handback(files, runtime=True)