-- 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(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 }