actualbudget/actual · error
Unknown locale ${language}
Error message
Unknown locale ${language} What it means
loadLanguage checks isLanguageAvailable(language), which tests whether the bundled language map has a key /locale/<language>.json. If the requested language has no bundled locale file, it throws 'Unknown locale <language>'. The available locales are determined at build time by the languages import map in i18n.ts.
Source
Thrown at packages/desktop-client/src/i18n.ts:18
import { initReactI18next } from 'react-i18next';
import * as Platform from '@actual-app/core/shared/platform';
import i18n from 'i18next';
import resourcesToBackend from 'i18next-resources-to-backend';
import { languages } from './languages';
export const availableLanguages = Platform.isPlaywright
? []
: Object.keys(languages).map(path => path.split('/')[2].split('.')[0]);
const isLanguageAvailable = (language: string) =>
Object.hasOwn(languages, `/locale/${language}.json`);
const loadLanguage = (language: string) => {
if (!isLanguageAvailable(language)) {
throw new Error(`Unknown locale ${language}`);
}
return languages[`/locale/${language}.json`]();
};
void i18n
.use(initReactI18next)
.use(resourcesToBackend(loadLanguage))
.init({
lng: 'en',
// allow keys to be phrases having `:`, `.`
nsSeparator: false,
keySeparator: false,
// do not load a fallback
fallbackLng: false,
interpolation: {
escapeValue: false,
},View on GitHub (pinned to d4334cb6e6)
Solutions
- Use one of the bundled locale codes listed in the languages object in packages/desktop-client/src/i18n.ts
- Normalize the stored language code to lowercase before calling loadLanguage
- If a new locale is needed, add its JSON file under the locale directory so Vite bundles it into the languages map
- Catch the error and fall back to 'en' instead of failing startup
Example fix
// before
await i18n.changeLanguage(userLanguage); // 'en-US' not bundled
// after
const available = Object.keys(languages).map(k => k.replace('/locale/', '').replace('.json', ''));
const lang = userLanguage.toLowerCase();
await i18n.changeLanguage(available.includes(lang) ? lang : 'en'); Defensive patterns
Strategy: validation
Validate before calling
const bundledLocales = new Set(Object.keys(languages).map(k => k.replace('/locale/', '').replace(/\.json$/, '')));
const resolveLanguage = (lang: string) => {
const l = lang.toLowerCase();
if (bundledLocales.has(l)) return l;
const base = l.split('-')[0];
return bundledLocales.has(base) ? base : 'en';
}; Type guard
const isAvailableLocale = (lang: string): lang is keyof typeof languages =>
Object.hasOwn(languages, `/locale/${lang}.json`); Try / catch
try {
await i18n.changeLanguage(userLanguage);
} catch (err) {
if (err.message.startsWith('Unknown locale')) {
await i18n.changeLanguage('en');
} else throw err;
} Prevention
- Normalize locale codes to lowercase and fall back to the base language ('pt-BR' -> 'pt')
- Derive any language picker options from the languages map so only bundled locales are offered
- When adding a locale, add the JSON file, the map entry, and a smoke test together
- Persist only validated locale codes to settings storage
When it happens
Trigger: Calling i18n.changeLanguage (or the loader path used by initReactI18next) with a locale code that is not in the languages map — e.g. 'en-gb', 'pt-BR' or a custom code with wrong casing when only 'en', 'es', etc. are bundled.
Common situations: Reading a language preference from the OS or storage that was never bundled; a user manually editing settings; adding a new locale to the UI list without adding its /locale/*.json file; case mismatches like 'EN' vs 'en'.
Related errors
- Error loading data into the spreadsheet.
- Unsupported summary type
- Unsupported schedule status for tooltip: ${scheduleStatus}
- Failed to get server config.
- Unrecognized bank-sync provider: ${acctRow.account_sync_sour
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/43c1d5ab902acd3f.
Report an issue: GitHub.