Files
zulip/static/js/i18n.ts
Priyank Patel 9418ae51fa ts: Convert i18n module to TypeScript.
We update our tsconfig target to ES2020 so that we don't get type
errors related to some properties not being in Intl that is used by
@formatjs/intl.
2021-06-22 09:09:36 -07:00

139 lines
4.5 KiB
TypeScript

// For documentation on i18n in Zulip, see:
// https://zulip.readthedocs.io/en/latest/translating/internationalization.html
import type {MessageDescriptor} from "@formatjs/intl";
import {DEFAULT_INTL_CONFIG, IntlErrorCode, createIntl, createIntlCache} from "@formatjs/intl";
import type {FormatXMLElementFn, PrimitiveType} from "intl-messageformat";
import _ from "lodash";
import {page_params} from "./page_params";
const cache = createIntlCache();
export const intl = createIntl(
{
locale: page_params.request_language,
defaultLocale: "en",
messages: page_params.translation_data,
onError: /* istanbul ignore next */ (error) => {
// Ignore complaints about untranslated strings that were
// added since the last sync-translations run.
if (error.code !== IntlErrorCode.MISSING_TRANSLATION) {
DEFAULT_INTL_CONFIG.onError(error);
}
},
},
cache,
);
export const $t = intl.formatMessage;
export const default_html_elements = Object.fromEntries(
["b", "code", "em", "i", "kbd", "p", "strong"].map((tag) => [
tag,
(content_html: string) => `<${tag}>${content_html}</${tag}>`,
]),
);
export function $t_html(
descriptor: MessageDescriptor,
values?: Record<string, PrimitiveType | FormatXMLElementFn<string, string>>,
): string {
return intl.formatMessage(descriptor, {
...default_html_elements,
...Object.fromEntries(
Object.entries(values ?? {}).map(([key, value]) => [
key,
typeof value === "function" ? value : _.escape(value?.toString()),
]),
),
});
}
export let language_list: typeof page_params["language_list"];
export function get_language_name(language_code: string): string {
const language_list_map: Record<string, string> = {};
// One-to-one mapping from code to name for all languages
for (const language of language_list) {
language_list_map[language.code] = language.name;
}
return language_list_map[language_code];
}
export function initialize(language_params: {language_list: typeof language_list}): void {
const language_list_raw = language_params.language_list;
// Limit offered languages to options with percentage translation >= 5%
language_list = [];
for (const language of language_list_raw) {
if (language.percent_translated === undefined || language.percent_translated >= 5) {
language_list.push({
code: language.code,
locale: language.locale,
name: language.name,
percent_translated: language.percent_translated,
});
}
}
}
// This formats language data for the language selection modal in a
// 2-column format.
type LanguageListColumn = {
[prop in "first" | "second"]?: {
code: string;
name: string;
name_with_percent: string;
selected: boolean;
};
};
export function get_language_list_columns(default_language: string): LanguageListColumn[] {
const formatted_list: LanguageListColumn[] = [];
const language_len = language_list.length;
const firsts_end = Math.floor(language_len / 2) + (language_len % 2);
const firsts = _.range(0, firsts_end);
const seconds = _.range(firsts_end, language_len);
const longest_zip: [number, number][] = [];
// Create a zip (itertool.zip_longest in python)
for (const value of firsts) {
longest_zip.push([value, seconds[value]]);
}
for (const row of longest_zip) {
const item: LanguageListColumn = {};
const zip_row = [
["first", row[0]],
["second", row[1]],
] as const;
for (const zip_value of zip_row) {
if (zip_value[1] !== undefined) {
const lang = language_list[zip_value[1]];
const name = lang.name;
let name_with_percent = name;
if (lang.percent_translated !== undefined) {
name_with_percent = name + " (" + lang.percent_translated + "%)";
}
let selected = false;
if (default_language === lang.code || default_language === lang.locale) {
selected = true;
}
item[zip_value[0]] = {
name,
code: lang.code,
name_with_percent,
selected,
};
}
}
formatted_list.push(item);
}
return formatted_list;
}