Skip to Content
services data_display

localization

Odoo 19 services — localization (core)

Live preview Interactive
Source excerpt web/static/src/core/l10n/localization_service.js
import { session } from "@web/session";
import { jsToPyLocale } from "@web/core/l10n/utils";
import { user } from "@web/core/user";
import { browser } from "../browser/browser";
import { registry } from "../registry";
import { strftimeToLuxonFormat } from "./dates";
import { localization } from "./localization";
import { rpcBus } from "../network/rpc";
import {
    translatedTerms,
    translatedTermsGlobal,
    translationLoaded,
    translationIsReady,
} from "./translation";
import { objectToUrlEncodedString } from "../utils/urls";
import { IndexedDB } from "../utils/indexed_db";

const { Settings } = luxon;

/** @type {[RegExp, string][]} */
const NUMBERING_SYSTEMS = [
    [/^ar-(sa|sy|001)$/i, "arab"],
    [/^bn/i, "beng"],
    [/^bo/i, "tibt"],
    // [/^fa/i, "Farsi (Persian)"], // No numberingSystem found in Intl
    // [/^(hi|mr|ne)/i, "Hindi"], // No numberingSystem found in Intl
    // [/^my/i, "Burmese"], // No numberingSystem found in Intl
    [/^pa-in/i, "guru"],
    [/^ta/i, "tamldec"],
    [/.*/i, "latn"],
];

export const localizationService = {
    start: async () => {
        const localizationDB = new IndexedDB("localization", session.registry_hash);
        const translationURL = session.translationURL || "/web/webclient/translations";
        const lang = jsToPyLocale(user.lang || document.documentElement.getAttribute("lang"));

        rpcBus.addEventListener("RPC:RESPONSE", (ev) => {
            const { method, model } = ev.detail.data.params || {};
            if (method === "lang_install" && model === "base.language.install") {
                rpcBus.trigger("CLEAR-CACHES");
            }
        });

        const fetchTranslations = async (hash) => {
            let queryString = objectToUrlEncodedString({ hash, lang });
            queryString = queryString.length > 0 ? `?${queryString}` : queryString;
            const response = await browser.fetch(`${translationURL}${queryString}`, {
                cache: "no-store",
            });
            if (!response.ok) {
                throw new Error("Error while fetching translations");
            }
            const result = await response.json();
            if (result.hash !== hash) {
                localizationDB.write(translationURL, JSON.stringify({ lang }), result);
                updateTranslations(result);
            }
        };

        const updateTranslations = (result) => {
            // Eventually, we want a new python route to return directly the good result.
            const terms = {};
            for (const addon of Object.keys(result.modules)) {
                terms[addon] = {};
                for (const message of result.modules[addon].messages) {
                    terms[addon][message.id] = message.string;
                    translatedTermsGlobal[message.id] = message.string;
                }
            }
            Object.assign(translatedTerms, terms);

            const userLocalization = result.lang_parameters;
            const dateFormat = strftimeToLuxonFormat(userLocalization.date_format);
            const timeFormat = strftimeToLuxonFormat(userLocalization.time_format);

            Object.assign(localization, {
                dateFormat,
                timeFormat,
                dateTimeFormat: `${dateFormat} ${timeFormat}`,
                decimalPoint: userLocalization.decimal_point,
                direction: userLocalization.direction,
                grouping: JSON.parse(userLocalization.grouping),
                multiLang: result.multi_lang,
                thousandsSep: userLocalization.thousands_sep,
                weekStart: userLocalization.week_start,
            });
        };

        const storedTranslations = await localizationDB.read(
            translationURL,
            JSON.stringify({ lang })
        );

        const translationProm = fetchTranslations(storedTranslations?.hash);
        if (storedTranslations) {
            updateTranslations(storedTranslations);
        } else {
            await translationProm;
        }

        translatedTerms[translationLoaded] = true;
        translationIsReady.resolve(true);

        const locale = user.lang || browser.navigator.language;
        Settings.defaultLocale = locale;
        for (const [re, numberingSystem] of NUMBERING_SYSTEMS) {
            if (re.test(locale)) {
                Settings.defaultNumberingSystem = numberingSystem;
                break;
            }
        }
        localization.code = jsToPyLocale(locale);
        return localization;
    },
};

registry.category("services").add("localization", localizationService);
Registry / API
Registry name
localization
Category
services
Module
web
Slug
localization
Nav group
data_display