Refactor i18n/intl utils, support icu only, remove renderText
This commit is contained in:
parent
e154d98688
commit
b76c7269f8
13 changed files with 361 additions and 6478 deletions
110
ts/util/setupI18n.tsx
Normal file
110
ts/util/setupI18n.tsx
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue