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

> Explore Uptime Kuma's technical i18n system. Discover how lazy loading and vue-i18n deliver dynamic translations efficiently, enhancing user experience.

- Repository: [Louis Lam/uptime-kuma](https://github.com/louislam/uptime-kuma)
- Tags: deep-dive
- Published: 2026-02-28

---

**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`](https://github.com/louislam/uptime-kuma/blob/main/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.

```js
// 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`](https://github.com/louislam/uptime-kuma/blob/main/src/i18n.js) (lines 54-109) initializes the message store with only English translations pre-loaded, while registering other languages by name only:

```js
// 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`](https://github.com/louislam/uptime-kuma/blob/main/src/mixins/lang.js), which uses Vue's `import.meta.glob` to create a mapping of all available JSON translation files:

```js
// 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`](https://github.com/louislam/uptime-kuma/blob/main/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.

```vue
<!-- 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`](https://github.com/louislam/uptime-kuma/blob/main/src/pages/StatusPage.vue) and [`src/pages/Settings.vue`](https://github.com/louislam/uptime-kuma/blob/main/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`](https://github.com/louislam/uptime-kuma/blob/main/src/util-frontend.js):

```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`](https://github.com/louislam/uptime-kuma/blob/main/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`](https://github.com/louislam/uptime-kuma/blob/main/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`](https://github.com/louislam/uptime-kuma/blob/main/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`](https://github.com/louislam/uptime-kuma/blob/main/xx-YY.json)), then add the locale code to the `languageList` object in [`src/i18n.js`](https://github.com/louislam/uptime-kuma/blob/main/src/i18n.js). The `import.meta.glob` pattern in [`src/mixins/lang.js`](https://github.com/louislam/uptime-kuma/blob/main/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`](https://github.com/louislam/uptime-kuma/blob/main/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`](https://github.com/louislam/uptime-kuma/blob/main/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.