source_utils_translateLocale.bs
' Locale resolution functions for the custom translation system.
' Separated from translate.bs because these depend on config.bs (getUserSetting)
' which is not available in all component scopes. These functions are only called
' from source/ context (Main.bs, session.bs) where config.bs is auto-scoped.
' Determine which locale to load using the fallback cascade.
' Pre-login (isPostLogin=false): device locale -> en_US
' Post-login (isPostLogin=true): user setting -> server language -> device locale -> en_US
' @param {boolean} isPostLogin - Whether a user is authenticated
' @param {string} serverLanguage - Language from server CustomPrefs.language (empty if unavailable)
' @return {string} Resolved locale code in standard format (e.g. "fr_CA", "en_US", "zh_Hans")
function resolveTranslationLocale(isPostLogin = false as boolean, serverLanguage = "" as string) as string
' Priority 1: Per-user language override (only available post-login)
if isPostLogin
userLocale = getUserSetting("translationLocale")
if isValidAndNotEmpty(userLocale) then return userLocale
end if
' Priority 2: Server CustomPrefs.language (post-login only)
' Normalize because Jellyfin may send various formats (e.g. "zh-CN", "pt-BR")
if isPostLogin and isValidAndNotEmpty(serverLanguage)
return normalizeLocaleCode(serverLanguage)
end if
' Priority 3: Roku device locale
deviceLocale = m.global.device.locale
if isValidAndNotEmpty(deviceLocale)
return mapRokuLocaleToTranslationLocale(deviceLocale)
end if
' Priority 4: Hardcoded default
return "en_US"
end function
' Convert Roku's locale format to our standard format.
' Roku sends underscore format (e.g. "en_US", "fr_CA", "zh_CN").
' Most pass through directly since they already match our convention,
' except Chinese locales which use script codes instead of region codes.
' @param {string} rokuLocale - Roku locale from roDeviceInfo.GetCurrentLocale()
' @return {string} Locale code in standard format
function mapRokuLocaleToTranslationLocale(rokuLocale as string) as string
if rokuLocale = "" then return "en_US"
' Chinese script code mapping — Roku uses region codes, we use script codes
rokuLocaleLower = LCase(rokuLocale)
if rokuLocaleLower = "zh_cn" then return "zh_Hans"
if rokuLocaleLower = "zh_tw" then return "zh_Hant"
if rokuLocaleLower = "zh_hk" then return "zh_Hant_HK"
' All other Roku locales already match our format (e.g. en_US, fr_CA, de_DE)
return rokuLocale
end function
' Normalize any locale code to our standard filename convention.
' Handles input from Jellyfin server, user settings, or any external source.
' Convention: base languages lowercase (e.g. "fr"), regional variants use
' underscore with uppercase region (e.g. "fr_CA"), Chinese uses script codes
' (e.g. "zh_Hans", "zh_Hant", "zh_Hant_HK"), numeric regions stay numeric (e.g. "es_419").
' @param {string} code - Locale code in any format (e.g. "fr-ca", "pt-BR", "zh-CN")
' @return {string} Normalized locale code matching our filename convention
function normalizeLocaleCode(code as string) as string
if code = "" then return "en_US"
' Preserve BCP-47 private-use subtags (x-...) before normalizing
if LCase(Left(code, 2)) = "x-" then return LCase(code)
' Replace hyphens with underscores for uniform processing
normalized = code.Replace("-", "_")
' Chinese script code mapping (case-insensitive)
normalizedLower = LCase(normalized)
if normalizedLower = "zh_cn" or normalizedLower = "zh_hans" then return "zh_Hans"
if normalizedLower = "zh_tw" or normalizedLower = "zh_hant" then return "zh_Hant"
if normalizedLower = "zh_hk" or normalizedLower = "zh_hant_hk" then return "zh_Hant_HK"
' Find the first underscore to split lang from region
underscorePos = inStr(1, normalized, "_")
if underscorePos > 0
' Regional locale: lowercase lang + uppercase region
lang = LCase(Left(normalized, underscorePos - 1))
region = Mid(normalized, underscorePos + 1)
return lang + "_" + UCase(region)
end if
' Base locale: lowercase
return LCase(normalized)
end function