Refactor i18n/intl utils, support icu only, remove renderText

This commit is contained in:
Jamie Kyle 2023-06-14 16:26:05 -07:00 committed by GitHub
parent e154d98688
commit b76c7269f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 361 additions and 6478 deletions

110
ts/util/setupI18n.tsx Normal file
View file

@ -0,0 +1,110 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import type { IntlShape } from 'react-intl';
import { createIntl, createIntlCache } from 'react-intl';
import type { LocaleMessageType, LocaleMessagesType } from '../types/I18N';
import type { LocalizerType } from '../types/Util';
import { strictAssert } from './assert';
import { Emojify } from '../components/conversation/Emojify';
export function isLocaleMessageType(
value: unknown
): value is LocaleMessageType {
return (
typeof value === 'object' &&
value != null &&
Object.hasOwn(value, 'messageformat')
);
}
function filterLegacyMessages(
messages: LocaleMessagesType
): Record<string, string> {
const icuMessages: Record<string, string> = {};
for (const [key, value] of Object.entries(messages)) {
if (isLocaleMessageType(value) && value.messageformat != null) {
icuMessages[key] = value.messageformat;
}
}
return icuMessages;
}
export function renderEmoji(parts: ReadonlyArray<unknown>): JSX.Element {
strictAssert(parts.length === 1, '<emoji> must contain only one child');
const text = parts[0];
strictAssert(typeof text === 'string', '<emoji> must contain only text');
return <Emojify text={text} />;
}
export function createCachedIntl(
locale: string,
icuMessages: Record<string, string>
): IntlShape {
const intlCache = createIntlCache();
const intl = createIntl(
{
locale: locale.replace('_', '-'), // normalize supported locales to browser format
messages: icuMessages,
defaultRichTextElements: {
emoji: renderEmoji,
},
},
intlCache
);
return intl;
}
export function setupI18n(
locale: string,
messages: LocaleMessagesType
): LocalizerType {
if (!locale) {
throw new Error('i18n: locale parameter is required');
}
if (!messages) {
throw new Error('i18n: messages parameter is required');
}
const intl = createCachedIntl(locale, filterLegacyMessages(messages));
const localizer: LocalizerType = (key, substitutions) => {
strictAssert(
!localizer.isLegacyFormat(key),
`i18n: Legacy message format is no longer supported "${key}"`
);
strictAssert(
!Array.isArray(substitutions),
`i18n: Substitutions must be an object for ICU message "${key}"`
);
const result = intl.formatMessage({ id: key }, substitutions);
strictAssert(
typeof result === 'string',
'i18n: Formatted translation result must be a string, must use <Intl/> component to render JSX'
);
strictAssert(result !== key, `i18n: missing translation for "${key}"`);
return result;
};
localizer.getIntl = () => {
return intl;
};
localizer.isLegacyFormat = (key: string) => {
return !key.startsWith('icu:');
};
localizer.getLocale = () => locale;
localizer.getLocaleMessages = () => messages;
localizer.getLocaleDirection = () => {
return window.getResolvedMessagesLocaleDirection();
};
return localizer;
}