Member languages

Your members, signed in or about to sign in, can use your app in a language Arc CMS does not ship, such as German. You declare it and write the translations; Arc CMS shows it on the member screens. The admin keeps Arc CMS's own languages.

Add a language

  1. Declare it in src/custom/languages.ts:

    import type { MemberLanguage } from '../app/core/i18n/member-languages';
    
    export const MEMBER_LANGUAGES: readonly MemberLanguage[] = [
        { code: 'de', label: 'Deutsch', locale: 'de-CH', localeData: () => import('@angular/common/locales/de-CH') },
    ];

    code names the translation file, label is the language's name in itself, locale is how dates and numbers are written, and localeData loads Angular's data for that locale (leave it out for an English locale). A mistake here stops npm run dev and npm run build with a message saying what to change.

  2. Translate it in src/custom/i18n/de.json, with the same keys as English. To see what is left to translate:

    npm run i18n:member -- --lang=de

    It lists every member-facing Arc CMS key and every key of your own src/custom/i18n/en.json that de.json lacks. A key you leave out shows in English, never as the key itself.

  3. Run the tests. The parity test fails naming each missing key. While a language is still being translated, add partial: true to its entry: the missing keys are then listed as a warning instead.

Members get English plus the languages you declare. Arc CMS's Hindi is offered to members only if you declare it too ({ code: 'hi', label: 'हिन्दी', locale: 'hi', localeData: () => import('@angular/common/locales/hi') }); its member keys are already translated.

Example: German for a front desk

An app that declared de above, with one screen of its own whose English is in src/custom/i18n/en.json:

{ "custom": { "desk": { "checkIn": "Check in a visitor" } } }

Its src/custom/i18n/de.json translates Arc CMS's member keys and its own, with the same nesting:

{
  "member": { "auth": { "continue": "Weiter" } },
  "user": { "nav": { "dashboard": "Übersicht" } },
  "custom": { "desk": { "checkIn": "Besucher anmelden" } }
}

With only these three keys, npm run i18n:member -- --lang=de lists the rest, and the parity test fails naming them until partial: true is set. A German-speaking member then sees "Weiter" on the sign-in button, "Übersicht" in the member menu and "Besucher anmelden" on your screen, and English for every key not yet written. The sign-in page has a language picker as soon as you declare a language (below). Elsewhere, put Arc CMS's picker on a page of yours, or call setMemberLanguage from a picker of your own:

import { setMemberLanguage } from '../../app/core/i18n/member-language.service';

// In your component, for a button such as <button (click)="german()">Deutsch</button>
german(): void {
  setMemberLanguage('de');
}

Which language a member sees

The language picker in the page header is the admin's: once you declare member languages it shows only in the admin area, since the admin's choice does not change member pages. Members choose on the sign-in page, or through the member picker on your own pages.

Build your own picker

<arc-member-language-picker> carries its own button styles and icon (inline SVG), so it works inside a Shadow DOM host; its menu opens in the page's overlay, outside the host, with the app's Material styles. To draw your own menu in your app's look, use MemberLanguageService (src/app/core/i18n/member-language.service.ts). These members are supported and stable:

MemberWhat it is
languagesEnglish and your declared languages, in order: each has code ('de'), label (its own name, "Deutsch") and locale ('de-CH'). Show a picker only when there is more than one.
activeLang()A signal: the code member screens show now.
activeLocale()A signal: its locale, for dates and numbers.
use(code, { reload })Switch member screens to that language at once and keep it on this device. A code you did not declare is ignored. reload: true reloads the page after.

Outside Angular code, setMemberLanguage(code) from the same file does what use() does.

The sign-in page

With member languages declared, the sign-in page shows the member picker, and both halves of the page follow it: the form through your translation files, and the brand panel beside it through the site's strings file for that language. For German, give your panel's data-arc-t keys in src/custom/site/strings/de.json; Arc CMS's own panel uses sign_in_title and sign_in_body:

{
  "sign_in_title": "Willkommen",
  "sign_in_body": "Melden Sie sich an oder erstellen Sie Ihr Konto, um fortzufahren."
}

Without that file the panel keeps the text as written. Hindi needs no file: Arc CMS ships one. With no member languages declared, there is no picker and the panel follows the page's language, as before.

With no languages declared, nothing changes: member screens follow the admin language as they always did.

Dates and numbers

The text switches at once. Angular's date and number pipes take the locale when the page loads, so they follow a new choice on the next load; setMemberLanguage('de', { reload: true }) reloads at once. For a value that must follow the switch without a reload, pass the locale yourself: {{ when | date: 'mediumDate' : undefined : member.activeLocale() }}. A person who opens the admin first and then a member page in the same visit keeps the admin's formats until the next load.

Which screens follow the language

Sign-in and sign-up (with email, phone and PIN, and Google), forgot password and PIN, the code steps, the profile with its sign-in methods, the account and membership page, the member area with its menu, dashboard, payments and premium pages, the install and update prompts, the feedback panel, the reload message, the page-not-found and no-access pages, and the cookie banner's own words (its "Learn more" link, and the buttons when the admin leaves their text empty). The sign-in errors Arc CMS shows itself, including Firebase's, are translated too, and so are the messages the sign-in functions send back, such as a wrong code, too many tries or a limit with the time to try again (member.auth.server_error.*, see Messages in the member's language).

Which keys are member-facing

The list is src/app/core/i18n/member-keys.ts: everything under member.*, user.* (the member area), common.pwa.* (install and update prompts), common.feedback.* (the feedback panel), common.version.* (the reload message), and a few shared keys such as common.actions.close. A test fails when a member screen uses a key the list does not cover.

Check every screen by eye

npm run i18n:pseudo -- --out=src/custom/i18n/zz.json

This writes a pseudo translation of the member keys, every word accented and in brackets. Declare { code: 'zz', label: 'Pseudo', locale: 'en-GB' }, call setMemberLanguage('zz'), and walk the member screens: any plain English left is text that is not translated yet. Remove the file and the entry afterwards.

Your own screens can be checked the same way in a spec: render them with pseudoTestingModule() from src/test/pseudo-language.ts and expect textNotInPseudo(fixture.nativeElement) to be empty. To include your own keys, pass your English: pseudoTestingModule(customEnglish), with customEnglish imported from src/custom/i18n/en.json.

What stays in English