Files
RuDS/specs/i18n.allium
2026-07-22 14:58:22 +02:00

106 lines
3.8 KiB
Plaintext

-- allium: 1
-- bDS Internationalization
-- Scope: core (Wave 0 onward — split localization is mandatory)
-- Distilled from: src/main/shared/i18n.ts, i18n/locales/*.json
value SupportedLanguage {
code: String
-- en, de, fr, it, es
flag: String
-- en=GB, de=DE, fr=FR, it=IT, es=ES
}
config {
supported_languages: Set<SupportedLanguage> = {
SupportedLanguage(code: "en", flag: "GB"),
SupportedLanguage(code: "de", flag: "DE"),
SupportedLanguage(code: "fr", flag: "FR"),
SupportedLanguage(code: "it", flag: "IT"),
SupportedLanguage(code: "es", flag: "ES")
}
default_language: String = "en"
}
-- ─── Split localization ─────────────────────────────────────
--
-- Two independent locale scopes exist per running app. They are
-- resolved by separate rules from separate inputs and may differ.
entity LocaleScopes {
-- Singleton holding the two locale scopes.
ui_locale: String
-- Follows the OS system locale; used for menus and app chrome
render_locale: String
-- Follows the active project's mainLanguage; used for
-- template rendering and generated content
}
surface LocaleSurface {
facing _: LocaleConsumer
provides:
OsLocaleDetected(os_locale)
ProjectMainLanguageResolved(main_language)
MenuTranslationRequested(item_key)
RenderTranslationRequested(label_key)
}
-- ─── Language normalization ─────────────────────────────────
--
-- Both scopes normalize their input the same way: take the base
-- language code, fall back to the default when unsupported.
rule NormalizeUiLocale {
when: OsLocaleDetected(os_locale)
let base = base_language_code(os_locale)
-- black box: substring of the input before the first "-"
-- ("en-US" -> "en")
let codes = config.supported_languages -> code
ensures:
if base in codes:
LocaleScopes.ui_locale = base
else:
LocaleScopes.ui_locale = config.default_language
}
rule NormalizeRenderLocale {
when: ProjectMainLanguageResolved(main_language)
let base = base_language_code(main_language)
let codes = config.supported_languages -> code
ensures:
if base in codes:
LocaleScopes.render_locale = base
else:
LocaleScopes.render_locale = config.default_language
}
-- ─── Translation lookup scopes ──────────────────────────────
rule TranslateMenuLabel {
when: MenuTranslationRequested(item_key)
-- Menus follow the OS convention: labels match the system
-- language (UI locale), never the render locale.
ensures: MenuLabelTranslated(item_key, locale: LocaleScopes.ui_locale)
}
rule TranslateRenderLabel {
when: RenderTranslationRequested(label_key)
-- Template rendering i18n strings (date formats, archive labels,
-- "older posts", "newer posts", etc.) come from the locale JSON
-- files for the render locale.
ensures: RenderLabelTranslated(label_key, locale: LocaleScopes.render_locale)
}
-- ─── Search stemmer coverage ────────────────────────────────
invariant SnowballStemmerCoverage {
-- ISO 639-1 codes with a dedicated Snowball algorithm
-- (stemmer_for_language in bds-core fts.rs); nb/nn/no share the
-- Norwegian stemmer, unknown codes fall back to English.
-- Every UI language must have real stemming, not the fallback.
let stemmed_codes = {"ar", "da", "de", "el", "en", "es", "fi", "fr",
"hu", "it", "nb", "nn", "no", "nl", "pt", "ro", "ru", "sv", "ta", "tr"}
for lang in config.supported_languages:
lang.code in stemmed_codes
}