feat: add best-effort PO language to Inno Setup language name conversion

This commit is contained in:
yyc12345 committed 2026-10-08 21:38:06 +08:00
1 parent 65d751c64c
commit 3393f1a63e
6 files changed
+353 -52

No files matched your search

+6 -1
View File
@@ -61,7 +61,12 @@ 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 language representation or convertion code for this target. The PO language tag carries over verbatim, and the pairing between a language and its base `.isl` file is expressed explicitly by the template author.
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).
## Bump Version Up
+31 -21
View File
@@ -10,31 +10,39 @@ Every component below renders with its **own environment**, so the filters avail
## Language Identification
Inno Setup has **no standard representation of language identifiers**: the `[Languages]` internal `Name:` and the message file names are both chosen freely by the packager. MetaGlot therefore performs **no conversion** for this target - the PO language tag (`zh_CN`, `en`) carries over verbatim, and the same tag serves as the single join key across everything the pipeline generates:
Inno Setup has **no standard representation of language identifiers**: the `[Languages]` internal `Name:` and the message file names are both chosen freely by the packager. What does exist is a set of **conventional file names** — the base names of the messages files distributed by the Inno Setup project itself, such as `ChineseSimplified` or `Japanese`.
- the `Name:` value of a `[Languages]` entry,
- the incremental `.isl` file name (e.g. `Strings\zh_CN.isl`),
- and the matching branch of the base file pairing in the `.iss` template (see below).
MetaGlot therefore exposes **two** language identifiers per language:
The pairing between a language and its base `.isl` - the official full translation whose `[LangOptions]` provides the language identity (the Select Language dialog name and the auto-detection identifier) - is knowledge of the template author and is expressed explicitly, for example:
- `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`.
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`.
Caveats:
- English has no dedicated file: `en` converts to `Default`, because `Default.isl` lives at the compiler root instead of the `Languages` subfolder.
- The converted name is the file **base name** (`ChineseSimplified`), not the `LanguageName` field inside the file (`Chinese (Simplified)`).
- Seven legacy community files ship with a `.islu` extension (Bengali, Hindi, Kazakh, Marathi, Nepali, Sinhala, Uyghur); the conversion returns base names only, and the extension is the template author's concern.
- Outdated community files predate the current Inno Setup message set; they still load, but their missing messages fall back to English.
With the conversion available, the base file pairing between a language and its base `.isl` — the official full translation whose `[LangOptions]` provides the language identity (the Select Language dialog name and the auto-detection identifier) — shrinks to one special case, the location of `Default.isl`:
```liquid
{% for lang in langs %}
{% case lang %}
{% when 'zh_CN' %}
{% assign base = 'compiler:Languages\ChineseSimplified.isl' %}
{% when 'ja' %}
{% assign base = 'compiler:Languages\Japanese.isl' %}
{% else %}
{% assign base = 'compiler:Default.isl' %}
{% endcase %}
Name: "{{ lang }}"; MessagesFile: "{{ base }},Strings\{{ lang }}.isl"
{% endfor %}
{% for lang in langs -%}
{%- if lang.lang and lang.lang != 'Default' -%}
{% assign base = 'compiler:Languages\' | append: lang.lang | append: '.isl' -%}
{%- else -%}
{% assign base = 'compiler:Default.isl' -%}
{%- endif -%}
{% assign incremental = lang.lang | default: lang.name -%}
Name: "{{ lang.name }}"; MessagesFile: "{{ base }},Strings\{{ incremental }}.isl"
{% endfor -%}
```
The multi-line decision block renders some blank lines into the INI output, which is harmless; Liquid's whitespace control markers (`{%-`/`-%}`) can remove them once used consistently across templates.
Languages whose `lang` is `null` fall to the `else` base (usually `compiler:Default.isl`) in the snippet above and inherit the English language identity. Keep that fallback (or a `case`/`when` on `name`) aligned with the languages you ship PO files for, and review the rendered `[Languages]` section after adding a new language.
Keep the `case` branches aligned with the languages you ship PO files for: an unmatched language falls to the `{% else %}` base (usually `compiler:Default.isl`) and inherits the English language identity. Review the rendered `[Languages]` section after adding a new language.
The whitespace control markers (`{%-`/`-%}`) keep the decision block from rendering blank lines into the INI output; use them consistently across the block.
## Path Template Contexts
@@ -52,12 +60,13 @@ The template is rendered once. Referencing any variable fails the render.
| Variable | Type | Description |
|---|---|---|
| `lang` | string | The PO language tag of the `.isl` messages file being placed, e.g. `en`, `zh_CN`. |
| `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`. |
The template is rendered once per language, for example:
```liquid
Strings/{{ lang }}.isl
Strings/{{ lang | default: name }}.isl
```
## File Render Contexts
@@ -69,7 +78,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 string | The PO language tag of every language, source language first. |
| `langs` | array of language | Every language, source language first. Each entry exposes `name` (the PO language tag) and `lang` (the conventional Inno Setup language name, or null). |
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:
@@ -86,6 +95,7 @@ The members of the current language are exposed at the top level. The structure
| Variable | Type | Description |
|---|---|---|
| `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. |
| `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.
+6 -8
View File
@@ -10,15 +10,13 @@ SolidCompression=yes
[Languages]
{% for lang in langs -%}
{% case lang -%}
{% when 'zh_CN' -%}
{% assign base = 'compiler:Languages\ChineseSimplified.isl' -%}
{% when 'ja' -%}
{% assign base = 'compiler:Languages\Japanese.isl' -%}
{% else -%}
{%- if lang.lang and lang.lang != 'Default' -%}
{% assign base = 'compiler:Languages\' | append: lang.lang | append: '.isl' -%}
{%- else -%}
{% assign base = 'compiler:Default.isl' -%}
{% endcase -%}
Name: "{{ lang }}"; MessagesFile: "{{ base }},Strings\{{ lang }}.isl"
{%- endif -%}
{% assign incremental = lang.lang | default: lang.name -%}
Name: "{{ lang.name }}"; MessagesFile: "{{ base }},Strings\{{ incremental }}.isl"
{% endfor -%}
[Tasks]
Name: "desktopicon"; Description: "{cm:task_desktop_icon}"
+70 -22
View File
@@ -2,8 +2,9 @@ import logging
from argparse import ArgumentParser
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from typing import Any, Optional
from ...filters import register_general_filters, register_inno_filters
from ...langmap import inno_lang_map
from ...manifest import load_manifest
from ...pofile import (
LanguagePack,
@@ -164,25 +165,63 @@ class _InnoIslPathContext:
"""The Liquid-facing context of one .isl path template rendering.
It is rendered once per language, with the PO language tag of the
messages file being placed exposed as ``lang``. Inno Setup has no
standard representation of language identifiers, so no conversion
happens: the tag is the Gettext PO tag verbatim.
messages file being placed exposed as ``name`` and the best-effort
conventional Inno Setup language name exposed as ``lang``. The
conversion is not normative and may yield ``None``, so path templates
typically render ``{{ lang | default: name }}``.
"""
__lang: str
__name: str
"""The PO language tag of the .isl messages file being placed."""
__lang: Optional[str]
"""The conventional Inno Setup language name, or None if unknown."""
def __init__(self, lang: str):
self.__lang = lang
def __init__(self, pack: LanguagePack):
inno = inno_lang_map.convert(pack.lang)
self.__name = pack.lang.value
self.__lang = inno.value if inno is not None else None
def keys(self) -> tuple[str, ...]:
"""Return the names of the top-level template variables."""
return ("lang",)
return ("lang", "name")
def __getitem__(self, key: str) -> object:
if key == "lang":
return self.__lang
raise KeyError(key)
match key:
case "lang":
return self.__lang
case "name":
return self.__name
case _:
raise KeyError(key)
class _InnoLanguageView:
"""The Liquid-facing view of one language inside the ``langs`` array of
the .iss template context.
It exposes the PO language tag as ``name`` and the best-effort
conventional Inno Setup language name as ``lang`` (``None`` when no
conventional name exists).
"""
__name: str
"""The PO language tag."""
__lang: Optional[str]
"""The conventional Inno Setup language name, or None if unknown."""
def __init__(self, pack: LanguagePack):
inno = inno_lang_map.convert(pack.lang)
self.__name = pack.lang.value
self.__lang = inno.value if inno is not None else None
def __getitem__(self, key: str) -> object:
match key:
case "name":
return self.__name
case "lang":
return self.__lang
case _:
raise KeyError(key)
class _InnoIslTemplateContext:
@@ -199,19 +238,25 @@ class _InnoIslTemplateContext:
"""The wrapped language pack."""
__strings: _InnoStringsView
"""The view of the pack's translations."""
__lang: Optional[str]
"""The conventional Inno Setup language name, or None if unknown."""
def __init__(self, pack: LanguagePack):
self.__pack = pack
self.__strings = _InnoStringsView(pack)
inno = inno_lang_map.convert(pack.lang)
self.__lang = inno.value if inno is not None else None
def keys(self) -> tuple[str, ...]:
"""Return the names of the exposed template variables."""
return ("name", "strings")
return ("name", "lang", "strings")
def __getitem__(self, key: str) -> object:
match key:
case "name":
return self.__pack.lang.value
case "lang":
return self.__lang
case "strings":
return self.__strings
case _:
@@ -224,12 +269,12 @@ class _InnoIssTemplateContext:
__default: _InnoIslTemplateContext
"""The view of the manifest's source language."""
__langs: tuple[str, ...]
"""The PO language tags of every language, source language first."""
__langs: tuple[_InnoLanguageView, ...]
"""The views of every language, source language first."""
def __init__(self, source: LanguagePack, langs: tuple[str, ...]):
def __init__(self, source: LanguagePack, langs: tuple[LanguagePack, ...]):
self.__default = _InnoIslTemplateContext(source)
self.__langs = langs
self.__langs = tuple(_InnoLanguageView(pack) for pack in langs)
def keys(self) -> tuple[str, ...]:
"""Return the names of the top-level template variables."""
@@ -256,9 +301,12 @@ def run(opts: InnoRenderOpts) -> None:
source = build_default_pack(manifest, manifest.source_language)
ordered = [source, *packs.values()]
# Inno Setup has no standard language identifier representation, so
# the PO language tags carry over verbatim.
langs = tuple(pack.lang.value for pack in ordered)
logging.info("rendering languages: %s", ", ".join(langs))
# the PO language tags carry over verbatim as the canonical identity.
# The conventional Inno Setup language names are derived per language
# by the context views, best effort.
logging.info(
"rendering languages: %s", ", ".join(str(pack.lang) for pack in ordered)
)
# Every component renders with its own environment, so their filters
# never interfere with each other.
@@ -279,13 +327,13 @@ def run(opts: InnoRenderOpts) -> None:
render(
iss_template_env,
opts.in_iss_template,
_InnoIssTemplateContext(source, langs),
_InnoIssTemplateContext(source, tuple(ordered)),
iss_out,
)
for pack, lang in zip(ordered, langs):
for pack in ordered:
isl_out = opts.out_dir / render_path(
isl_path_env, opts.in_isl_path, _InnoIslPathContext(lang)
isl_path_env, opts.in_isl_path, _InnoIslPathContext(pack)
)
render(
isl_template_env,
+62
View File
@@ -470,3 +470,65 @@ class WinLcid:
return hash(self.__value)
# endregion
class InnoLang:
"""
Represents the human-readable name of an Inno Setup language, i.e. the
conventional base name of a messages (``.isl``) file such as ``Japanese``
or ``ChineseSimplified``.
Inno Setup itself defines no grammar for these names: the official and
community translations simply use CamelCase English words. This class
constrains them conservatively: a non-empty run of ASCII letters and
spaces that starts with a letter.
Examples:
InnoLang("Japanese") # valid
InnoLang("Chinese Simplified") # valid (spaces allowed)
InnoLang(" Japanese") # invalid (must start with a letter)
InnoLang("") # invalid (empty)
InnoLang("日本語") # invalid (non-ASCII characters)
"""
__PATTERN: ClassVar[re.Pattern] = re.compile(r"^[A-Za-z][A-Za-z ]*$")
__value: str
"""The language name."""
def __init__(self, value: str):
if not self.__PATTERN.match(value):
raise ValueError(
f"Invalid Inno Setup language name: {value!r}. "
f"Expected only ASCII letters and spaces"
)
self.__value = value
# region: Properties
@property
def value(self) -> str:
"""The language name."""
return self.__value
# endregion
# region: Object methods
def __str__(self) -> str:
return self.__value
def __repr__(self) -> str:
return f"InnoLang({self.__value!r})"
def __eq__(self, other) -> bool:
if self is other:
return True
if not isinstance(other, InnoLang):
return NotImplemented
return self.__value == other.__value
def __hash__(self) -> int:
return hash(self.__value)
# endregion
+178
View File
@@ -0,0 +1,178 @@
from ..langid import InnoLang, PoLang
# 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 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 table is authored name-oriented (_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
# 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 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 table records
# base names only, and the extension is the template author's concern.
# fmt: off
_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",),
# Unofficial 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.
# Unofficial 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 _NAME_KEYS.
# 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 name, keys in _NAME_KEYS.items() for key in keys
}
if len(_INNO_NAMES) != sum(len(keys) for keys in _NAME_KEYS.values()):
raise ValueError("duplicate PO language key(s) in the Inno Setup name table")
# Gettext-style script designator words mapped to ISO 15924 script codes.
# This is not the full ISO 15924 table - it only contains the words that can
# hit entries in _INNO_NAMES above. Extend it as needed.
_SCRIPT_WORDS: dict[str, str] = {
"latin": "Latn",
"cyrillic": "Cyrl",
"arabic": "Arab",
}
def convert(lang: PoLang) -> InnoLang | None:
"""
Convert a PO file language to a conventional Inno Setup language name.
The conversion is a best effort: the language, country and variant
components are hyphenated into candidate keys, variant-aware forms
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
language without a conventional name converts to ``None``, and the
caller falls back to the PO language tag.
:param lang: The PO file language.
:return: The conventional Inno Setup language name, or ``None`` if no
conventional name exists for the language.
"""
candidates: list[str] = []
if lang.variant is not None:
script = _SCRIPT_WORDS.get(lang.variant)
if script is not None:
# Script interpretation: 'sr_RS@latin' -> 'sr-Latn-RS'.
candidates.append(f"{lang.language}-{script}-{lang.country}")
candidates.append(f"{lang.language}-{script}")
else:
# Variant interpretation: 'ca_ES@valencia' -> 'ca-ES-valencia'.
candidates.append(f"{lang.language}-{lang.country}-{lang.variant}")
candidates.append(f"{lang.language}-{lang.variant}")
if lang.country is not None:
candidates.append(f"{lang.language}-{lang.country}")
candidates.append(lang.language)
for candidate in candidates:
if candidate in _INNO_NAMES:
return _INNO_NAMES[candidate]
return None