How Uptime Kuma's Multi-Language (i18n) System Works: A Technical Deep Dive

Uptime Kuma implements internationalization using vue-i18n with a lazy-loading architecture that ships only English in the core bundle, dynamically fetching translation JSON files on demand while persisting user preferences in localStorage.

Uptime Kuma is a popular open-source monitoring solution built on Vue.js that serves a global user base through its robust multi-language i18n system. The architecture prioritizes performance by deferring non-English translations until needed, while providing seamless runtime locale switching and full right-to-left (RTL) language support. This implementation demonstrates how modern Vue applications can handle dozens of languages without bloating the initial JavaScript payload.

Locale Detection and Fallback Strategy

The entry point for all internationalization logic resides in src/i18n.js, where the currentLocale() function determines which language to display. This function implements a priority-based detection system that checks multiple sources in sequence.

// src/i18n.js (lines 71-96)
export function currentLocale() {
    for (const locale of [localStorage.locale, navigator.language, ...navigator.languages]) {
        if (!locale) continue;
        if (locale in messages) return locale;               // exact match
        if (locale.length === 2) {                           // e.g. "fr"
            const regional = `${locale}-${locale.toUpperCase()}`;
            if (regional in messages) return regional;
        } else {
            const generic = locale.slice(0, 2);              // e.g. "en-US" → "en"
            if (generic in messages) return generic;
        }
    }
    return "en";
}

The detection cascade prioritizes localStorage.locale first, allowing returning users to retain their explicit language choice. If no stored preference exists, the function iterates through navigator.language and navigator.languages, attempting exact matches before falling back to generic two-letter codes. If no match is found, the system defaults to "en".

The Message Store and RTL Support

The i18n instance creation in src/i18n.js (lines 54-109) initializes the message store with only English translations pre-loaded, while registering other languages by name only:

// src/i18n.js
let messages = { en };
for (let lang in languageList) {
    messages[lang] = { languageName: languageList[lang] };
}
const rtlLangs = ["he-IL", "fa", "ar-SY", "ur"];
export const localeDirection = () =>
    rtlLangs.includes(currentLocale()) ? "rtl" : "ltr";

export const i18n = createI18n({
    locale: currentLocale(),
    fallbackLocale: "en",
    silentFallbackWarn: true,
    silentTranslationWarn: true,
    messages,
});

This approach keeps the initial bundle small by populating only the languageName property for non-English locales. The localeDirection() function determines text direction by checking against the rtlLangs array, returning "rtl" for Hebrew, Persian, Arabic, and Urdu, and "ltr" for all others.

Lazy Loading Language Packs with the Language Mixin

Dynamic translation loading is handled by the language mixin in src/mixins/lang.js, which uses Vue's import.meta.glob to create a mapping of all available JSON translation files:

// src/mixins/lang.js
const langModules = import.meta.glob("../lang/*.json");

export default {
    data() { return { language: currentLocale() }; },

    async created() {
        if (this.language !== "en") await this.changeLang(this.language);
    },

    watch: { async language(lang) { await this.changeLang(lang); } },

    methods: {
        async changeLang(lang) {
            const message = (await langModules[`../lang/${lang}.json`]()).default;
            this.$i18n.setLocaleMessage(lang, message);
            this.$i18n.locale = lang;
            localStorage.locale = lang;
            setPageLocale();                // updates HTML <html lang="">
            timeDurationFormatter.updateLocale(lang);
        },
    },
};

When a component using this mixin is created, it checks if the current language requires loading (any non-English locale). The changeLang() method dynamically imports the corresponding JSON file from src/lang/, injects the messages into the vue-i18n instance using setLocaleMessage(), and persists the selection to localStorage. The setPageLocale() helper from src/util-frontend.js updates the HTML document's lang attribute and direction.

Using Translations in Vue Components

Components access translated strings through the $t() method provided by vue-i18n. The system supports simple key lookups and parameterized strings with placeholders.

<!-- Example from src/pages/Setup.vue -->
<label for="username" class="form-label">{{ $t('Username') }}</label>
<input id="username" v-model="username" :placeholder="$t('Username')" />

<!-- Dynamic text with placeholders -->
{{ $t('Refresh Interval Description', [config.autoRefreshInterval]) }}

The $t() function automatically falls back to English if the translation key is missing in the currently loaded locale, thanks to the fallbackLocale: "en" configuration. Components throughout the application, including src/pages/StatusPage.vue and src/pages/Settings.vue, rely on this API for all user-facing text.

Handling Right-to-Left (RTL) Layouts

For languages requiring right-to-left text direction, the system updates the document root attribute through src/util-frontend.js:

// src/util-frontend.js
import { localeDirection } from "./i18n";
document.documentElement.setAttribute("dir", localeDirection());

This ensures that the entire UI layout adjusts appropriately for RTL languages without requiring component-specific modifications. The direction calculation happens reactively whenever the locale changes through the changeLang() method.

Summary

  • Uptime Kuma uses vue-i18n as the foundation for its multi-language i18n system, configured in src/i18n.js with English as the fallback locale.
  • Lazy loading minimizes bundle size by fetching translation JSON files on demand via the language mixin in src/mixins/lang.js using import.meta.glob.
  • Locale detection prioritizes user preferences stored in localStorage, then browser settings, with intelligent fallback to generic language codes.
  • RTL support is built into the core system through the localeDirection() function and automatic HTML attribute updates.
  • Runtime switching is handled reactively through Vue watchers that load new translations and persist choices without page reloads.

Frequently Asked Questions

How does Uptime Kuma detect which language to display?

The currentLocale() function in src/i18n.js checks three sources in order: the localStorage.locale value for returning users, the browser's navigator.language property, and the navigator.languages array. It attempts exact matches first, then falls back to generic two-letter language codes (e.g., "en-US" becomes "en") before defaulting to English if no match exists in the supported language list.

Can I add a new language translation to Uptime Kuma without modifying the core code?

Yes. Create a new JSON file in src/lang/ following the existing naming convention (e.g., xx-YY.json), then add the locale code to the languageList object in src/i18n.js. The import.meta.glob pattern in src/mixins/lang.js will automatically detect and load the new translation file when users select that language, requiring no changes to the loading logic.

Why does Uptime Kuma only load English by default?

The architecture intentionally ships only English translations in the initial bundle to minimize startup time and bandwidth usage. All other languages are lazy-loaded as needed when a user selects a non-English locale or when the currentLocale() function detects a supported language from browser settings. This approach keeps the core application lightweight while supporting 50+ languages.

How does the system handle right-to-left languages like Arabic or Hebrew?

The localeDirection() function in src/i18n.js checks if the current locale exists in the rtlLangs array (containing "he-IL", "fa", "ar-SY", and "ur"). When the language changes, the setPageLocale() utility in src/util-frontend.js applies the appropriate "rtl" or "ltr" value to the HTML document's dir attribute, ensuring proper text alignment and layout direction across the entire application.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →