diff --git a/DEVNOTE.md b/DEVNOTE.md index e4755e2..291df16 100644 --- a/DEVNOTE.md +++ b/DEVNOTE.md @@ -61,12 +61,7 @@ InnoSetup installation involves multiple file formats. All formats of them can b Based on these document, we create corresponding Liquid filters for both files, since they share the same message value format. -Unlike the other targets, InnoSetup has no standard representation of language identifiers: both the internal language names declared in the `[Languages]` section and the message file names are chosen freely by the packager, as its official document says. So there is no normative conversion for this target - the PO language tag carries over verbatim as the canonical identity. What MetaGlot adds on top is a best-effort conversion to the conventional language file names distributed by the InnoSetup project. We transcribe the name list from its [unofficial translations page](https://jrsoftware.org/files/istrans/), which covers both the official translations bundled with the compiler and the community provided ones (including the outdated ones). The list was fetched on **2026-10-08** and reflects Inno Setup **6.5.0+**. The names are represented by `InnoLang`, which constrains them to ASCII letters and spaces. Notes kept during the transcription: - -- The conventional name is the file base name, not the `LanguageName` field inside the file: `ChineseSimplified` vs `Chinese (Simplified)`. -- English has no dedicated file: `en` converts to `Default`, because `Default.isl` sits at the compiler root, not in the `Languages` subfolder. -- Seven legacy community files ship with a `.islu` extension (Bengali, Hindi, Kazakh, Marathi, Nepali, Sinhala, Uyghur); the table records base names only. -- The pairing between a language and its base `.isl` file remains knowledge of the template author for languages outside the transcription table (the conversion yields nothing, and the PO language tag is the fallback). +InnoSetup used human-readable name can be found at this [unofficial translations page](https://jrsoftware.org/files/istrans/). However this page can not reflect the true circumstance of InnoSetup (some entries are only for the latest version and some entries are wrong). So I sample the layout of `Languages` directory on my own in 2 different InnoSetup verison, **6.7.1** and **7.1.0**, to build the final conversion map. ## Bump Version Up diff --git a/doc/render-context/inno.md b/doc/render-context/inno.md index c9aedd6..b2b8148 100644 --- a/doc/render-context/inno.md +++ b/doc/render-context/inno.md @@ -16,9 +16,11 @@ MetaGlot therefore exposes **two** language identifiers and **one** provenance f - `name` — the PO language tag (`zh_CN`, `en`), carried over verbatim. It is the canonical join key across everything the pipeline generates: the `Name:` value of a `[Languages]` entry, the language ordering, and the fallback identifier when the conversion below fails. - `lang` — the best-effort conversion of the PO tag to a conventional Inno Setup language name (`zh_CN` → `ChineseSimplified`, `ja`/`ja_JP` → `Japanese`). It is `null` when no conventional name is known for the language; handle those languages with `{{ lang | default: name }}`, or with a `case`/`when` block on `name`. -- `is_official` — whether `lang` belongs to an official translation bundled with Inno Setup (`true`) or to a community provided translation (`false`). It is also `false` when `lang` is `null`, since there is no official file to reference then. Only the official files come with an Inno Setup installation; the community files must be downloaded from the [translations page](https://jrsoftware.org/files/istrans/) and shipped with the installer yourself — or the base file falls back to `compiler:Default.isl` for users with a bare Inno Setup installation. +- `is_official` — whether `lang` belongs to a translation bundled with the **targeted** Inno Setup version, as given by the required `--inno-version` command line option (`true`), or not (`false`). It is also `false` when `lang` is `null`, since there is no official file to reference then. Only the bundled files come with an Inno Setup installation; the others must be downloaded from the [translations page](https://jrsoftware.org/files/istrans/) and shipped with the installer yourself — or the base file falls back to `compiler:Default.isl` for users with a bare Inno Setup installation. -The conversion table transcribes the official translations bundled with Inno Setup together with the community translations listed on the [unofficial translations page](https://jrsoftware.org/files/istrans/) — including the outdated ones — so adding a new language with a known name requires no template edits. Country-sensitive languages are disambiguated: `zh`/`zh_SG` → `ChineseSimplified` but `zh_TW`/`zh_HK`/`zh_MO` → `ChineseTraditional`; `pt` → `Portuguese` but `pt_BR` → `BrazilianPortuguese`; `en` → `Default` but `en_GB` → `EnglishBritish`; `no`/`nb` → `Norwegian` but `nn` → `NorwegianNynorsk`; `sr` → `SerbianCyrillic` but `sr_RS@latin` → `SerbianLatin`; `ca_ES@valencia` → `Valencian`. +The conversion table transcribes the conventional names from the [unofficial translations page](https://jrsoftware.org/files/istrans/) — official and community lists alike, including the outdated ones — so adding a new language with a known name requires no template edits. Country-sensitive languages are disambiguated: `zh`/`zh_SG` → `ChineseSimplified` but `zh_TW`/`zh_HK`/`zh_MO` → `ChineseTraditional`; `pt` → `Portuguese` but `pt_BR` → `BrazilianPortuguese`; `en` → `Default` but `en_GB` → `EnglishBritish`; `no`/`nb` → `Norwegian` but `nn` → `NorwegianNynorsk`; `sr` → `SerbianCyrillic` but `sr_RS@latin` → `SerbianLatin`; `ca_ES@valencia` → `Valencian`. + +The officiality behind `is_official` is transcribed from the `Languages` folder listings of Inno Setup **6.7.1** and **7.1.0**. The two differ in four names: `ChineseSimplified`, `ChineseTraditional`, `Lithuanian` and `Thai` are bundled with Inno Setup 7 only, so they count as official solely when `--inno-version 7` is given. `Icelandic` is listed as an official translation by the translations page, but neither listing ships it, so it is never official. Caveats: @@ -68,7 +70,7 @@ The template is rendered once. Referencing any variable fails the render. |---|---|---| | `lang` | string or null | The conventional Inno Setup language name of the `.isl` messages file being placed, e.g. `ChineseSimplified`; `null` when no conventional name exists. | | `name` | string | The PO language tag of the `.isl` messages file being placed, e.g. `en`, `zh_CN`. | -| `is_official` | boolean | Whether the conventional name belongs to an official translation bundled with Inno Setup; `false` for community provided names and when `lang` is `null`. | +| `is_official` | boolean | Whether the conventional name is bundled with the Inno Setup version targeted by `--inno-version`; `false` otherwise and when `lang` is `null`. | The template is rendered once per language, for example: @@ -85,7 +87,7 @@ The `--iss-template` and `--isl-template` arguments point to template files. Bes | Variable | Type | Description | |---|---|---| | `default` | language | The manifest's source language entry, see below. | -| `langs` | array of language | Every language, source language first. Each entry exposes `name` (the PO language tag), `lang` (the conventional Inno Setup language name, or null) and `is_official` (whether the name belongs to an official translation). | +| `langs` | array of language | Every language, source language first. Each entry exposes `name` (the PO language tag), `lang` (the conventional Inno Setup language name, or null) and `is_official` (whether the name is bundled with the Inno Setup version targeted by `--inno-version`). | The `.iss` script references the localized text through `{cm:...}` constants (e.g. `AppName={cm:app_name}`) and enumerates `[Languages]` from `langs` with the base file pairing shown above. `default` is available for values written directly instead of via constants: @@ -103,7 +105,7 @@ The members of the current language are exposed at the top level. The structure |---|---|---| | `name` | string | The PO language tag of this `.isl` messages file, e.g. `en_US`, `zh_CN`. | | `lang` | string or null | The conventional Inno Setup language name of this `.isl` messages file, e.g. `Japanese`; `null` when no conventional name exists. | -| `is_official` | boolean | Whether the conventional name belongs to an official translation bundled with Inno Setup; `false` for community provided names and when `lang` is `null`. | +| `is_official` | boolean | Whether the conventional name is bundled with the Inno Setup version targeted by `--inno-version`; `false` otherwise and when `lang` is `null`. | | `strings` | mapping of string to string | Maps every manifest entry key to its resolved text, e.g. `strings.app_name`. | The resolved text of an entry falls back to the manifest source string when the entry is untranslated, missing from the PO file, or marked fuzzy. The template is rendered once per language. diff --git a/src/metaglot/cmds/render/inno.py b/src/metaglot/cmds/render/inno.py index 1dfde90..d284ac2 100644 --- a/src/metaglot/cmds/render/inno.py +++ b/src/metaglot/cmds/render/inno.py @@ -1,4 +1,5 @@ import logging +import enum from argparse import ArgumentParser from dataclasses import dataclass from pathlib import Path @@ -20,9 +21,19 @@ _ISS_TEMPLATE_DEST = "iss_template" _ISL_TEMPLATE_DEST = "isl_template" _ISS_PATH_DEST = "iss_path" _ISL_PATH_DEST = "isl_path" +_INNO_VERSION_DEST = "inno_version" _OUTPUT_DEST = "output" +class InnoVersion(enum.StrEnum): + """The type for accepting Inno Setup version in command line.""" + + Inno6 = "6" + """Inno Setup 6 series.""" + Inno7 = "7" + """Inno Setup 7 series.""" + + @dataclass(frozen=True) class InnoRenderOpts: in_manifest: Path @@ -37,6 +48,8 @@ class InnoRenderOpts: """The Liquid template resolving the output path of the .iss script file.""" in_isl_path: str """The Liquid template resolving the output path of every .isl messages file.""" + inno_version: InnoVersion + """The major version of Inno Setup the rendering targets.""" out_dir: Path """The path of the output folder holding every rendered file.""" @@ -63,6 +76,18 @@ def register(parser: ArgumentParser) -> None: help="The PO files to read, each may be a glob pattern.", metavar="GLOB", ) + parser.add_argument( + "--inno-version", + dest=_INNO_VERSION_DEST, + action="store", + type=InnoVersion, + choices=[v for v in InnoVersion], + required=True, + help=( + "The major version of Inno Setup the rendering targets; it " + "decides which conventional language names count as official." + ), + ) parser.add_argument( "--iss-template", dest=_ISS_TEMPLATE_DEST, @@ -129,6 +154,7 @@ def parse(args: dict[str, Any]) -> InnoRenderOpts: args[_ISL_TEMPLATE_DEST], args[_ISS_PATH_DEST], args[_ISL_PATH_DEST], + args[_INNO_VERSION_DEST], args[_OUTPUT_DEST], ) @@ -161,17 +187,27 @@ class _InnoIssPathContext: raise KeyError(key) -def _resolve_inno_lang(pack: LanguagePack) -> tuple[Optional[str], bool]: +def _resolve_inno_lang(pack: LanguagePack, inno_version: InnoVersion) -> tuple[Optional[str], bool]: """Resolve a language pack to its conventional Inno Setup language - name and officiality. + name and officiality, against the targeted Inno Setup version. :param pack: The language pack to resolve. + :param inno_version: The Inno Setup major version the rendering + targets. :return: A pair of the conventional name (``None`` when no conventional name exists) and whether the name belongs to an - official translation bundled with Inno Setup (always ``False`` - when the name is ``None``). + official translation bundled with the targeted version (always + ``False`` when the name is ``None``). """ - result = inno_lang_map.convert(pack.lang) + match inno_version: + case InnoVersion.Inno6: + map_inno_version = inno_lang_map.InnoVersion.Inno6 + case InnoVersion.Inno7: + map_inno_version = inno_lang_map.InnoVersion.Inno7 + case _: + raise ValueError(f"unhandled Inno Setup version: {inno_version}") + + result = inno_lang_map.convert(pack.lang, map_inno_version) name = result.name.value if result.name is not None else None return name, result.official @@ -182,8 +218,9 @@ class _InnoIslPathContext: It is rendered once per language, with the PO language tag of the messages file being placed exposed as ``name``, the best-effort conventional Inno Setup language name exposed as ``lang`` and its - officiality exposed as ``is_official``. The conversion is not - normative and may yield ``None``, so path templates typically render + officiality in the targeted Inno Setup version exposed as + ``is_official``. The conversion is not normative and may yield + ``None``, so path templates typically render ``{{ lang | default: name }}``. """ @@ -194,9 +231,9 @@ class _InnoIslPathContext: __official: bool """Whether the conventional name is an official translation.""" - def __init__(self, pack: LanguagePack): + def __init__(self, pack: LanguagePack, inno_version: InnoVersion): self.__name = pack.lang.value - self.__lang, self.__official = _resolve_inno_lang(pack) + self.__lang, self.__official = _resolve_inno_lang(pack, inno_version) def keys(self) -> tuple[str, ...]: """Return the names of the top-level template variables.""" @@ -220,7 +257,8 @@ class _InnoLanguageView: It exposes the PO language tag as ``name``, the best-effort conventional Inno Setup language name as ``lang`` (``None`` when no - conventional name exists) and its officiality as ``is_official``. + conventional name exists) and its officiality in the targeted Inno + Setup version as ``is_official``. """ __name: str @@ -230,9 +268,9 @@ class _InnoLanguageView: __official: bool """Whether the conventional name is an official translation.""" - def __init__(self, pack: LanguagePack): + def __init__(self, pack: LanguagePack, inno_version: InnoVersion): self.__name = pack.lang.value - self.__lang, self.__official = _resolve_inno_lang(pack) + self.__lang, self.__official = _resolve_inno_lang(pack, inno_version) def __getitem__(self, key: str) -> object: match key: @@ -265,10 +303,10 @@ class _InnoIslTemplateContext: __official: bool """Whether the conventional name is an official translation.""" - def __init__(self, pack: LanguagePack): + def __init__(self, pack: LanguagePack, inno_version: InnoVersion): self.__pack = pack self.__strings = _InnoStringsView(pack) - self.__lang, self.__official = _resolve_inno_lang(pack) + self.__lang, self.__official = _resolve_inno_lang(pack, inno_version) def keys(self) -> tuple[str, ...]: """Return the names of the exposed template variables.""" @@ -297,9 +335,16 @@ class _InnoIssTemplateContext: __langs: tuple[_InnoLanguageView, ...] """The views of every language, source language first.""" - def __init__(self, source: LanguagePack, langs: tuple[LanguagePack, ...]): - self.__default = _InnoIslTemplateContext(source) - self.__langs = tuple(_InnoLanguageView(pack) for pack in langs) + def __init__( + self, + source: LanguagePack, + langs: tuple[LanguagePack, ...], + inno_version: InnoVersion, + ): + self.__default = _InnoIslTemplateContext(source, inno_version) + self.__langs = tuple( + _InnoLanguageView(pack, inno_version) for pack in langs + ) def keys(self) -> tuple[str, ...]: """Return the names of the top-level template variables.""" @@ -352,17 +397,19 @@ def run(opts: InnoRenderOpts) -> None: render( iss_template_env, opts.in_iss_template, - _InnoIssTemplateContext(source, tuple(ordered)), + _InnoIssTemplateContext(source, tuple(ordered), opts.inno_version), iss_out, ) for pack in ordered: isl_out = opts.out_dir / render_path( - isl_path_env, opts.in_isl_path, _InnoIslPathContext(pack) + isl_path_env, + opts.in_isl_path, + _InnoIslPathContext(pack, opts.inno_version), ) render( isl_template_env, opts.in_isl_template, - _InnoIslTemplateContext(pack), + _InnoIslTemplateContext(pack, opts.inno_version), isl_out, ) diff --git a/src/metaglot/langmap/inno_lang_map.py b/src/metaglot/langmap/inno_lang_map.py index 4261118..9fe2fe8 100644 --- a/src/metaglot/langmap/inno_lang_map.py +++ b/src/metaglot/langmap/inno_lang_map.py @@ -1,153 +1,189 @@ +import enum from dataclasses import dataclass from ..langid import InnoLang, PoLang + +class InnoVersion(enum.IntFlag): + """ + Represents the major versions of Inno Setup, as a flag set. + + A flag combination marks the set of Inno Setup versions a conventional + language name is official in; ``Nothing`` marks a name that no + transcribed Inno Setup version bundles. + """ + + Nothing = 0 + Inno6 = enum.auto() + Inno7 = enum.auto() + + +@dataclass(frozen=True) +class _InnoLangProp: + """The properties of one conventional Inno Setup language name.""" + + official_vers: InnoVersion + """The Inno Setup versions the name is official in.""" + ids: tuple[str, ...] + """Every PO language key converting to the name.""" + + # YYC MARK: # Inno Setup language files have no standardized names: both the internal # names of the [Languages] section and the messages file names are chosen -# freely by the packager. The tables below transcribe the *conventional* +# freely by the packager. The table below transcribes the *conventional* # base names of the messages files distributed by the Inno Setup project # itself, so a PO language can be paired with them without template-side # case blocks. # -# The official translations are bundled with Inno Setup (in the compiler's -# 'Languages' subfolder), and the unofficial ones are community maintained. -# Both lists were fetched from https://jrsoftware.org/files/istrans/ on -# 2026-10-08, reflecting Inno Setup 6.5.0+. The official/unofficial split -# is kept as two separate seed tables, so it stays machine-readable: the -# officiality of a conversion is exposed to templates as 'is_official', -# letting them serve users with a bare Inno Setup installation. +# The names and the community list come from +# https://jrsoftware.org/files/istrans/ (fetched 2026-10-08). The +# officiality of each name is transcribed from the 'Languages' folder +# listings of the Inno Setup compilers themselves: 6.7.1 backs +# InnoVersion.Inno6 and 7.1.0 backs InnoVersion.Inno7 (both listings +# received 2026-10-12). The two listings differ in four names: +# ChineseSimplified, ChineseTraditional, Lithuanian and Thai are bundled +# since Inno Setup 7 only. # -# The tables are authored name-oriented (_OFFICIAL_NAME_KEYS and -# _UNOFFICIAL_NAME_KEYS below, one row per conventional name holding every -# PO language key converting to it) and the key-oriented lookup used at -# runtime (_INNO_NAMES) is derived from them, so a name shared by several -# keys - like ChineseSimplified ('zh', 'zh-SG') - is declared exactly once. -# The keys are hyphenated PO language components ('ll', 'll-CC', or -# variant-aware forms). Keys needing country disambiguation are spelled -# out; every other language is reachable through the bare 'll' fallback. -# Outdated unofficial translations are still transcribed - an outdated -# base file is better than an English-only installer - with their minimum -# supported Inno Setup version noted in a trailing comment. +# The table is authored name-oriented (_LANGS below, one row per +# conventional name holding its officiality and every PO language key +# converting to it) and the structures used at runtime (the key-oriented +# lookup _INNO_NAMES and the per-version official sets) are derived from +# it, so a name shared by several keys - like ChineseSimplified ('zh', +# 'zh-SG') - is declared exactly once. The keys are hyphenated PO +# language components ('ll', 'll-CC', or variant-aware forms). Keys +# needing country disambiguation are spelled out; every other language is +# reachable through the bare 'll' fallback. Outdated unofficial +# translations are still transcribed - an outdated base file is better +# than an English-only installer - with their minimum supported Inno +# Setup version noted in a trailing comment. # # Transcription notes: # - English has no dedicated file: 'Default.isl' lives at the compiler # root, not in the 'Languages' subfolder, so 'en' maps to 'Default'. +# The folder listings do not cover it; it is treated as official in +# every version. +# - Known contradiction: the istrans page lists Icelandic as an official +# translation, but neither the 6.7.1 nor the 7.1.0 'Languages' folder +# ships it. The folder listings win here, so Icelandic is official in +# no version. # - The conventional name is the file BASE name, not the 'LanguageName' # field inside the file ('ChineseSimplified' vs 'Chinese (Simplified)'). -# - Seven legacy unofficial files ship with a '.islu' extension (Bengali, -# Hindi, Kazakh, Marathi, Nepali, Sinhala, Uyghur); the tables record +# - Seven legacy community files ship with a '.islu' extension (Bengali, +# Hindi, Kazakh, Marathi, Nepali, Sinhala, Uyghur); the table records # base names only, and the extension is the template author's concern. # fmt: off -_OFFICIAL_NAME_KEYS: dict[InnoLang, tuple[str, ...]] = { - # Official translations (bundled with Inno Setup). - InnoLang("Default"): ("en",), # compiler:Default.isl, at the compiler root. - InnoLang("Arabic"): ("ar",), - InnoLang("Armenian"): ("hy",), - InnoLang("Bulgarian"): ("bg",), - InnoLang("Catalan"): ("ca",), - InnoLang("ChineseSimplified"): ("zh", "zh-SG"), - InnoLang("ChineseTraditional"): ("zh-TW", "zh-HK", "zh-MO"), - InnoLang("Corsican"): ("co",), - InnoLang("Czech"): ("cs",), - InnoLang("Danish"): ("da",), - InnoLang("Dutch"): ("nl",), - InnoLang("Finnish"): ("fi",), - InnoLang("French"): ("fr",), - InnoLang("German"): ("de",), - InnoLang("Hebrew"): ("he",), - InnoLang("Hungarian"): ("hu",), - InnoLang("Icelandic"): ("is",), - InnoLang("Italian"): ("it",), - InnoLang("Japanese"): ("ja",), - InnoLang("Korean"): ("ko",), - InnoLang("Lithuanian"): ("lt",), - InnoLang("Norwegian"): ("no", "nb"), - InnoLang("Polish"): ("pl",), - InnoLang("Portuguese"): ("pt",), - InnoLang("BrazilianPortuguese"): ("pt-BR",), - InnoLang("Russian"): ("ru",), - InnoLang("Slovak"): ("sk",), - InnoLang("Slovenian"): ("sl",), - InnoLang("Spanish"): ("es",), - InnoLang("Swedish"): ("sv",), - InnoLang("Tamil"): ("ta",), - InnoLang("Thai"): ("th",), - InnoLang("Turkish"): ("tr",), - InnoLang("Ukrainian"): ("uk",), +_LANGS: dict[InnoLang, _InnoLangProp] = { + # Official in every transcribed version (the 6.7.1 and 7.1.0 + # 'Languages' folder intersection, plus Default). + InnoLang("Default"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("en",)), # compiler:Default.isl, at the compiler root. + InnoLang("Arabic"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("ar",)), + InnoLang("Armenian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("hy",)), + InnoLang("BrazilianPortuguese"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("pt-BR",)), + InnoLang("Bulgarian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("bg",)), + InnoLang("Catalan"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("ca",)), + InnoLang("Corsican"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("co",)), + InnoLang("Czech"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("cs",)), + InnoLang("Danish"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("da",)), + InnoLang("Dutch"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("nl",)), + InnoLang("Finnish"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("fi",)), + InnoLang("French"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("fr",)), + InnoLang("German"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("de",)), + InnoLang("Hebrew"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("he",)), + InnoLang("Hungarian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("hu",)), + InnoLang("Italian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("it",)), + InnoLang("Japanese"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("ja",)), + InnoLang("Korean"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("ko",)), + InnoLang("Norwegian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("no", "nb")), + InnoLang("Polish"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("pl",)), + InnoLang("Portuguese"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("pt",)), + InnoLang("Russian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("ru",)), + InnoLang("Slovak"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("sk",)), + InnoLang("Slovenian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("sl",)), + InnoLang("Spanish"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("es",)), + InnoLang("Swedish"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("sv",)), + InnoLang("Tamil"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("ta",)), + InnoLang("Turkish"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("tr",)), + InnoLang("Ukrainian"): _InnoLangProp(InnoVersion.Inno6 | InnoVersion.Inno7, ("uk",)), + # Official since Inno Setup 7 only. + InnoLang("ChineseSimplified"): _InnoLangProp(InnoVersion.Inno7, ("zh", "zh-SG")), + InnoLang("ChineseTraditional"): _InnoLangProp(InnoVersion.Inno7, ("zh-TW", "zh-HK", "zh-MO")), + InnoLang("Lithuanian"): _InnoLangProp(InnoVersion.Inno7, ("lt",)), + InnoLang("Thai"): _InnoLangProp(InnoVersion.Inno7, ("th",)), + # Official in no transcribed version (community provided). + InnoLang("Icelandic"): _InnoLangProp(InnoVersion.Nothing, ("is",)), # istrans lists it official; no transcribed bundle ships it. + # Community maintained, up to date with the current Inno Setup. + InnoLang("Afrikaans"): _InnoLangProp(InnoVersion.Nothing, ("af",)), + InnoLang("Basque"): _InnoLangProp(InnoVersion.Nothing, ("eu",)), + InnoLang("Croatian"): _InnoLangProp(InnoVersion.Nothing, ("hr",)), + InnoLang("EnglishBritish"): _InnoLangProp(InnoVersion.Nothing, ("en-GB",)), + InnoLang("Galician"): _InnoLangProp(InnoVersion.Nothing, ("gl",)), + InnoLang("Greek"): _InnoLangProp(InnoVersion.Nothing, ("el",)), + InnoLang("Esperanto"): _InnoLangProp(InnoVersion.Nothing, ("eo",)), + InnoLang("Indonesian"): _InnoLangProp(InnoVersion.Nothing, ("id",)), + InnoLang("Farsi"): _InnoLangProp(InnoVersion.Nothing, ("fa",)), + InnoLang("Vietnamese"): _InnoLangProp(InnoVersion.Nothing, ("vi",)), + InnoLang("SerbianCyrillic"): _InnoLangProp(InnoVersion.Nothing, ("sr",)), # Cyrillic is the default script. + InnoLang("SerbianLatin"): _InnoLangProp(InnoVersion.Nothing, ("sr-Latn",)), # e.g. sr_RS@latin. + # Community maintained, outdated for the current Inno Setup. Minimum versions noted. + InnoLang("Abkhazian"): _InnoLangProp(InnoVersion.Nothing, ("ab",)), # outdated: 6.4.0+ + InnoLang("Albanian"): _InnoLangProp(InnoVersion.Nothing, ("sq",)), # outdated: 6.4.0+ + InnoLang("Azerbaijan"): _InnoLangProp(InnoVersion.Nothing, ("az",)), # outdated: 6.4.0+ + InnoLang("Bosnian"): _InnoLangProp(InnoVersion.Nothing, ("bs",)), # outdated: 6.4.0+ + InnoLang("Ewe"): _InnoLangProp(InnoVersion.Nothing, ("ee",)), # outdated: 6.4.0+ + InnoLang("Belarusian"): _InnoLangProp(InnoVersion.Nothing, ("be",)), # outdated: 6.1.0+ + InnoLang("Georgian"): _InnoLangProp(InnoVersion.Nothing, ("ka",)), # outdated: 6.1.0+ + InnoLang("Latvian"): _InnoLangProp(InnoVersion.Nothing, ("lv",)), # outdated: 6.1.0+ + InnoLang("Montenegrin"): _InnoLangProp(InnoVersion.Nothing, ("cnr",)), # outdated: 6.1.0+ + InnoLang("Romanian"): _InnoLangProp(InnoVersion.Nothing, ("ro",)), # outdated: 6.1.0+ + InnoLang("Urdu"): _InnoLangProp(InnoVersion.Nothing, ("ur",)), # outdated: 6.1.0+ + InnoLang("Uyghur"): _InnoLangProp(InnoVersion.Nothing, ("ug",)), # outdated: 6.1.0+, ships as .islu + InnoLang("ScottishGaelic"): _InnoLangProp(InnoVersion.Nothing, ("gd",)), # outdated: 6.0.0+ + InnoLang("Bengali"): _InnoLangProp(InnoVersion.Nothing, ("bn",)), # outdated: 5.5.3+, ships as .islu + InnoLang("Estonian"): _InnoLangProp(InnoVersion.Nothing, ("et",)), # outdated: 5.5.3+ + InnoLang("Hindi"): _InnoLangProp(InnoVersion.Nothing, ("hi",)), # outdated: 5.5.3+, ships as .islu + InnoLang("Marathi"): _InnoLangProp(InnoVersion.Nothing, ("mr",)), # outdated: 5.5.3+, ships as .islu + InnoLang("Nepali"): _InnoLangProp(InnoVersion.Nothing, ("ne",)), # outdated: 5.5.3+, ships as .islu + InnoLang("Sinhala"): _InnoLangProp(InnoVersion.Nothing, ("si",)), # outdated: 5.5.3+, ships as .islu + InnoLang("Uzbek"): _InnoLangProp(InnoVersion.Nothing, ("uz",)), # outdated: 5.5.3+ + InnoLang("Valencian"): _InnoLangProp(InnoVersion.Nothing, ("ca-ES-valencia",)), # outdated: 5.5.3+ + InnoLang("Kazakh"): _InnoLangProp(InnoVersion.Nothing, ("kk",)), # outdated: 5.5.0+, ships as .islu + InnoLang("Kurdish"): _InnoLangProp(InnoVersion.Nothing, ("ku",)), # outdated: 5.5.0+ + InnoLang("Tatar"): _InnoLangProp(InnoVersion.Nothing, ("tt",)), # outdated: 5.5.0+ + InnoLang("Ligurian"): _InnoLangProp(InnoVersion.Nothing, ("lij",)), # outdated: 5.1.11+ + InnoLang("Luxemburgish"): _InnoLangProp(InnoVersion.Nothing, ("lb",)), # outdated: 5.1.11+ + InnoLang("Macedonian"): _InnoLangProp(InnoVersion.Nothing, ("mk",)), # outdated: 5.1.11+ + InnoLang("Mongolian"): _InnoLangProp(InnoVersion.Nothing, ("mn",)), # outdated: 5.1.11+ + InnoLang("Occitan"): _InnoLangProp(InnoVersion.Nothing, ("oc",)), # outdated: 5.1.11+ + InnoLang("Malaysian"): _InnoLangProp(InnoVersion.Nothing, ("ms",)), # outdated: 5.1.0+ + InnoLang("Asturian"): _InnoLangProp(InnoVersion.Nothing, ("ast",)), # outdated: 4.0.5+ + InnoLang("NorwegianNynorsk"): _InnoLangProp(InnoVersion.Nothing, ("nn",)), # outdated: 4.0.5+ } # fmt: on -# fmt: off -_UNOFFICIAL_NAME_KEYS: dict[InnoLang, tuple[str, ...]] = { - # Community maintained translations, up to date with 6.5.0+. - InnoLang("Afrikaans"): ("af",), - InnoLang("Basque"): ("eu",), - InnoLang("Croatian"): ("hr",), - InnoLang("EnglishBritish"): ("en-GB",), - InnoLang("Galician"): ("gl",), - InnoLang("Greek"): ("el",), - InnoLang("Esperanto"): ("eo",), - InnoLang("Indonesian"): ("id",), - InnoLang("Farsi"): ("fa",), - InnoLang("Vietnamese"): ("vi",), - InnoLang("SerbianCyrillic"): ("sr",), # Cyrillic is the default script. - InnoLang("SerbianLatin"): ("sr-Latn",), # e.g. sr_RS@latin. - # Community maintained translations, outdated for 6.5.0. Minimum versions noted. - InnoLang("Abkhazian"): ("ab",), # outdated: 6.4.0+ - InnoLang("Albanian"): ("sq",), # outdated: 6.4.0+ - InnoLang("Azerbaijan"): ("az",), # outdated: 6.4.0+ - InnoLang("Bosnian"): ("bs",), # outdated: 6.4.0+ - InnoLang("Ewe"): ("ee",), # outdated: 6.4.0+ - InnoLang("Belarusian"): ("be",), # outdated: 6.1.0+ - InnoLang("Georgian"): ("ka",), # outdated: 6.1.0+ - InnoLang("Latvian"): ("lv",), # outdated: 6.1.0+ - InnoLang("Montenegrin"): ("cnr",), # outdated: 6.1.0+ - InnoLang("Romanian"): ("ro",), # outdated: 6.1.0+ - InnoLang("Urdu"): ("ur",), # outdated: 6.1.0+ - InnoLang("Uyghur"): ("ug",), # outdated: 6.1.0+, ships as .islu - InnoLang("ScottishGaelic"): ("gd",), # outdated: 6.0.0+ - InnoLang("Bengali"): ("bn",), # outdated: 5.5.3+, ships as .islu - InnoLang("Estonian"): ("et",), # outdated: 5.5.3+ - InnoLang("Hindi"): ("hi",), # outdated: 5.5.3+, ships as .islu - InnoLang("Marathi"): ("mr",), # outdated: 5.5.3+, ships as .islu - InnoLang("Nepali"): ("ne",), # outdated: 5.5.3+, ships as .islu - InnoLang("Sinhala"): ("si",), # outdated: 5.5.3+, ships as .islu - InnoLang("Uzbek"): ("uz",), # outdated: 5.5.3+ - InnoLang("Valencian"): ("ca-ES-valencia",), # outdated: 5.5.3+ - InnoLang("Kazakh"): ("kk",), # outdated: 5.5.0+, ships as .islu - InnoLang("Kurdish"): ("ku",), # outdated: 5.5.0+ - InnoLang("Tatar"): ("tt",), # outdated: 5.5.0+ - InnoLang("Ligurian"): ("lij",), # outdated: 5.1.11+ - InnoLang("Luxemburgish"): ("lb",), # outdated: 5.1.11+ - InnoLang("Macedonian"): ("mk",), # outdated: 5.1.11+ - InnoLang("Mongolian"): ("mn",), # outdated: 5.1.11+ - InnoLang("Occitan"): ("oc",), # outdated: 5.1.11+ - InnoLang("Malaysian"): ("ms",), # outdated: 5.1.0+ - InnoLang("Asturian"): ("ast",), # outdated: 4.0.5+ - InnoLang("NorwegianNynorsk"): ("nn",), # outdated: 4.0.5+ -} -# fmt: on - -# The key-oriented lookup table used at runtime, derived from both seed -# tables. The duplicate check makes a transcription mistake - the same PO -# language key attached to two names - fail at import time instead of -# silently overriding in the comprehension below. +# The key-oriented lookup table used at runtime. The duplicate check makes +# a transcription mistake - the same PO language key attached to two names +# - fail at import time instead of silently overriding in the +# comprehension below. _INNO_NAMES: dict[str, InnoLang] = { - key: name - for names in (_OFFICIAL_NAME_KEYS, _UNOFFICIAL_NAME_KEYS) - for name, keys in names.items() - for key in keys + key: name for name, prop in _LANGS.items() for key in prop.ids } -if len(_INNO_NAMES) != sum( - len(keys) for names in (_OFFICIAL_NAME_KEYS, _UNOFFICIAL_NAME_KEYS) for keys in names.values() -): - raise ValueError("duplicate PO language key(s) in the Inno Setup name table") +if len(_INNO_NAMES) != sum(len(prop.ids) for prop in _LANGS.values()): + raise ValueError("duplicate PO language key(s) in the Inno Setup language table") -# The conventional names that belong to official translations, i.e. the -# messages files bundled with the Inno Setup compiler itself. -_OFFICIAL_NAMES: frozenset[InnoLang] = frozenset(_OFFICIAL_NAME_KEYS) +# The conventional names bundled with each transcribed Inno Setup version, +# derived from the officiality flags. +_OFFICIAL_NAMES_V6: frozenset[InnoLang] = frozenset( + name for name, prop in _LANGS.items() if prop.official_vers & InnoVersion.Inno6 +) +_OFFICIAL_NAMES_V7: frozenset[InnoLang] = frozenset( + name for name, prop in _LANGS.items() if prop.official_vers & InnoVersion.Inno7 +) +_OFFICIAL_NAMES: dict[InnoVersion, frozenset[InnoLang]] = { + InnoVersion.Inno6: _OFFICIAL_NAMES_V6, + InnoVersion.Inno7: _OFFICIAL_NAMES_V7, +} @dataclass(frozen=True) @@ -160,7 +196,8 @@ class InnoConversion: official: bool """Whether the name belongs to an official translation bundled with - Inno Setup. Always ``False`` when ``name`` is ``None``.""" + the targeted Inno Setup version. Always ``False`` when ``name`` is + ``None``.""" # Gettext-style script designator words mapped to ISO 15924 script codes. @@ -173,7 +210,7 @@ _SCRIPT_WORDS: dict[str, str] = { } -def convert(lang: PoLang) -> InnoConversion: +def convert(lang: PoLang, inno_version: InnoVersion) -> InnoConversion: """ Convert a PO file language to a conventional Inno Setup language name. @@ -182,15 +219,19 @@ def convert(lang: PoLang) -> InnoConversion: first (the PO variant slot may hold a script designator, interpreted via the script word table), then the country form, then the bare language form. The first candidate hitting the transcription table - wins. Unlike the other targets, no exception is ever raised - a + wins. Unlike the other targets, a missing name never raises - a language without a conventional name converts to an empty result (``name`` is ``None``, ``official`` is ``False``), and the caller - falls back to the PO language tag. + falls back to the PO language tag. The officiality is judged against + the given Inno Setup version. :param lang: The PO file language. + :param inno_version: The Inno Setup major version the rendering + targets. It must be a single-version member, not a combination + flag. :return: The conversion result holding the conventional Inno Setup language name (``None`` if none exists) and whether it belongs to - an official translation bundled with Inno Setup. + an official translation bundled with the targeted version. """ candidates: list[str] = [] if lang.variant is not None: @@ -209,5 +250,5 @@ def convert(lang: PoLang) -> InnoConversion: for candidate in candidates: name = _INNO_NAMES.get(candidate) if name is not None: - return InnoConversion(name, name in _OFFICIAL_NAMES) + return InnoConversion(name, name in _OFFICIAL_NAMES[inno_version]) return InnoConversion(None, False)