Files
documenso/packages/lib/utils/i18n.ts
David Nguyen f199183c78 feat: improve translation coverage (#1427)
Improves translation coverage across the app.
2024-11-01 10:57:32 +11:00

123 lines
3.3 KiB
TypeScript

import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
import type { I18n, MessageDescriptor } from '@lingui/core';
import { IS_APP_WEB, IS_APP_WEB_I18N_ENABLED } from '../constants/app';
import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
import { APP_I18N_OPTIONS } from '../constants/i18n';
export async function dynamicActivate(i18nInstance: I18n, locale: string) {
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
const context = IS_APP_WEB ? 'web' : 'marketing';
let { messages } = await import(`../translations/${locale}/${context}.${extension}`);
// Dirty way to load common messages for development since it's not compiled.
if (process.env.NODE_ENV === 'development') {
const commonMessages = await import(`../translations/${locale}/common.${extension}`);
messages = {
...messages,
...commonMessages.messages,
};
}
i18nInstance.loadAndActivate({ locale, messages });
}
const parseLanguageFromLocale = (locale: string): SupportedLanguageCodes | null => {
const [language, _country] = locale.split('-');
const foundSupportedLanguage = APP_I18N_OPTIONS.supportedLangs.find(
(lang): lang is SupportedLanguageCodes => lang === language,
);
if (!foundSupportedLanguage) {
return null;
}
return foundSupportedLanguage;
};
/**
* Extract the language if supported from the cookies header.
*
* Returns `null` if not supported or not found.
*/
export const extractLocaleDataFromCookies = (
cookies: ReadonlyRequestCookies,
): SupportedLanguageCodes | null => {
const preferredLocale = cookies.get('language')?.value || '';
const language = parseLanguageFromLocale(preferredLocale || '');
if (!language) {
return null;
}
return language;
};
/**
* Extracts the language from the `accept-language` header.
*/
export const extractLocaleDataFromHeaders = (
headers: Headers,
): { lang: SupportedLanguageCodes | null; locales: string[] } => {
const headerLocales = (headers.get('accept-language') ?? '').split(',');
const language = parseLanguageFromLocale(headerLocales[0]);
return {
lang: language,
locales: [headerLocales[0]],
};
};
type ExtractLocaleDataOptions = {
headers: Headers;
cookies: ReadonlyRequestCookies;
};
/**
* Extract the supported language from the cookies, then header if not found.
*
* Will return the default fallback language if not found.
*/
export const extractLocaleData = ({
headers,
cookies,
}: ExtractLocaleDataOptions): I18nLocaleData => {
let lang: SupportedLanguageCodes | null = extractLocaleDataFromCookies(cookies);
const langHeader = extractLocaleDataFromHeaders(headers);
if (!lang && langHeader?.lang) {
lang = langHeader.lang;
}
// Override web app to be English.
if (!IS_APP_WEB_I18N_ENABLED && IS_APP_WEB) {
lang = 'en';
}
// Filter out locales that are not valid.
const locales = (langHeader?.locales ?? []).filter((locale) => {
try {
new Intl.Locale(locale);
return true;
} catch {
return false;
}
});
return {
lang: lang || APP_I18N_OPTIONS.sourceLang,
locales,
};
};
export const parseMessageDescriptor = (_: I18n['_'], value: string | MessageDescriptor) => {
return typeof value === 'string' ? value : _(value);
};