Loading a language

With messages.fr.json from Extracting messages, define a provider in a separate file to choose and load the language:

// localisation.ts
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import {
  LOCALE_ID,
  inject,
  makeEnvironmentProviders,
  provideAppInitializer,
  type EnvironmentProviders,
} from '@angular/core';
import { loadTranslations } from '@angular/localize';
import { Locale } from '@ng-native/expo/locale';
import { getItem } from 'expo-secure-store';
import fr from './locale/messages.fr.json';

/** The language the source text is written in. There is nothing to load for it. */
const SOURCE = 'en';

const TRANSLATIONS: Record<string, Record<string, string>> = {
  fr: fr.translations,
};

registerLocaleData(localeFr);

/** The first language in the list that this app has, or the source language. */
export function chooseLanguage(preferred: readonly (string | null)[]): string {
  return (
    preferred.find((code) => code === SOURCE || (code != null && code in TRANSLATIONS)) ?? SOURCE
  );
}

export function provideLocalisation(): EnvironmentProviders {
  return makeEnvironmentProviders([
    {
      provide: LOCALE_ID,
      useFactory: () =>
        chooseLanguage([
          getItem('language'),
          ...inject(Locale)
            .locales()
            .map((locale) => locale.languageCode),
        ]),
    },
    provideAppInitializer(() => {
      const messages = TRANSLATIONS[inject(LOCALE_ID)];
      if (messages) loadTranslations(messages);
    }),
  ]);
}

Pass it to mount:

// src/main.ts
const app = mount(Number(rootTag), App, getFabricUIManager(), {
  // ...processColor, conditions, tokens, resolveAssetSource as before
  providers: [provideLocalisation()],
});

LOCALE_ID controls both translation loading and Angular pipe formatting. It selects the in-app choice first (see Switching language), then device languages in preference order. Use Locale.locales(), not Locale.locale(): if the user prefers German then French, an app without German should choose French rather than English.

mount runs initialisers before creating the root component, so translations must load synchronously. It starts promise-returning initialisers without awaiting them; network-fetched translations would arrive after the first frame and template evaluation. Import translations so Metro bundles them.

Missing translations fall back to source text and log No translation found, so an incomplete file ships a partly English screen rather than a broken one. Unsupported device languages fall back to SOURCE.

Next: Switching language covers changing LOCALE_ID after startup.

Angular components as native iOS and Android views, built and shipped with Expo.

An alpha. MIT licensed. Sponsor its development.

An independent project, not affiliated with or endorsed by Google, the Angular team or Expo. Angular is a trademark of Google LLC.