Don't import emoji-datasource in main
This commit is contained in:
parent
f5953d0986
commit
ab226f29a9
3 changed files with 162 additions and 124 deletions
|
@ -6,7 +6,8 @@ import { readFileSync } from 'fs';
|
|||
import { merge } from 'lodash';
|
||||
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
||||
import { z } from 'zod';
|
||||
import { setupI18n } from '../ts/util/setupI18n';
|
||||
import { setupI18n } from '../ts/util/setupI18nMain';
|
||||
import { shouldNeverBeCalled } from '../ts/util/shouldNeverBeCalled';
|
||||
|
||||
import type { LoggerType } from '../ts/types/Logging';
|
||||
import type { HourCyclePreference, LocaleMessagesType } from '../ts/types/I18N';
|
||||
|
@ -142,7 +143,9 @@ export function load({
|
|||
|
||||
// We start with english, then overwrite that with anything present in locale
|
||||
const finalMessages = merge(englishMessages, matchedLocaleMessages);
|
||||
const i18n = setupI18n(matchedLocale, finalMessages);
|
||||
const i18n = setupI18n(matchedLocale, finalMessages, {
|
||||
renderEmojify: shouldNeverBeCalled,
|
||||
});
|
||||
const direction =
|
||||
localeDirectionTestingOverride ?? getLocaleDirection(matchedLocale, logger);
|
||||
logger.info(`locale: Text info direction for ${matchedLocale}: ${direction}`);
|
||||
|
|
|
@ -1,44 +1,18 @@
|
|||
// Copyright 2018 Signal Messenger, LLC
|
||||
// Copyright 2024 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,
|
||||
ICUStringMessageParamsByKeyType,
|
||||
} from '../types/Util';
|
||||
import { strictAssert } from './assert';
|
||||
import React from 'react';
|
||||
import type { LocaleMessagesType } from '../types/I18N';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { Emojify } from '../components/conversation/Emojify';
|
||||
import * as log from '../logging/log';
|
||||
import * as Errors from '../types/errors';
|
||||
import { Environment, getEnvironment } from '../environment';
|
||||
import { bidiIsolate } from './unicodeBidi';
|
||||
import {
|
||||
createCachedIntl as createCachedIntlMain,
|
||||
setupI18n as setupI18nMain,
|
||||
} from './setupI18nMain';
|
||||
import { strictAssert } from './assert';
|
||||
|
||||
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 { isLocaleMessageType } from './setupI18nMain';
|
||||
|
||||
export function renderEmojify(parts: ReadonlyArray<unknown>): JSX.Element {
|
||||
strictAssert(parts.length === 1, '<emojify> must contain only one child');
|
||||
|
@ -51,96 +25,12 @@ 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: {
|
||||
emojify: renderEmojify,
|
||||
},
|
||||
onError(error) {
|
||||
log.error('intl.onError', Errors.toLogFormat(error));
|
||||
},
|
||||
onWarn(warning) {
|
||||
if (
|
||||
getEnvironment() === Environment.Test &&
|
||||
warning.includes(
|
||||
// This warning is very noisy during tests
|
||||
'"defaultRichTextElements" was specified but "message" was not pre-compiled.'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
log.warn('intl.onWarn', warning);
|
||||
},
|
||||
},
|
||||
intlCache
|
||||
);
|
||||
return intl;
|
||||
}
|
||||
|
||||
function normalizeSubstitutions<
|
||||
Substitutions extends Record<string, string | number | Date> | undefined
|
||||
>(substitutions?: Substitutions): Substitutions | undefined {
|
||||
if (!substitutions) {
|
||||
return;
|
||||
}
|
||||
const normalized: Record<string, string | number | Date> = {};
|
||||
const entries = Object.entries(substitutions);
|
||||
if (entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const [key, value] of entries) {
|
||||
if (typeof value === 'string') {
|
||||
normalized[key] = bidiIsolate(value);
|
||||
} else {
|
||||
normalized[key] = value;
|
||||
}
|
||||
}
|
||||
return normalized as Substitutions;
|
||||
return createCachedIntlMain(locale, icuMessages, { renderEmojify });
|
||||
}
|
||||
|
||||
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 extends keyof ICUStringMessageParamsByKeyType
|
||||
>(
|
||||
key: Key,
|
||||
substitutions: ICUStringMessageParamsByKeyType[Key]
|
||||
) => {
|
||||
const result = intl.formatMessage(
|
||||
{ id: key },
|
||||
normalizeSubstitutions(substitutions)
|
||||
);
|
||||
|
||||
strictAssert(result !== key, `i18n: missing translation for "${key}"`);
|
||||
|
||||
return result;
|
||||
}) as LocalizerType;
|
||||
|
||||
localizer.getIntl = () => {
|
||||
return intl;
|
||||
};
|
||||
localizer.getLocale = () => locale;
|
||||
localizer.getLocaleMessages = () => messages;
|
||||
localizer.getLocaleDirection = () => {
|
||||
return window.SignalContext.getResolvedMessagesLocaleDirection();
|
||||
};
|
||||
localizer.getHourCyclePreference = () => {
|
||||
return window.SignalContext.getHourCyclePreference();
|
||||
};
|
||||
|
||||
return localizer;
|
||||
return setupI18nMain(locale, messages, { renderEmojify });
|
||||
}
|
||||
|
|
145
ts/util/setupI18nMain.ts
Normal file
145
ts/util/setupI18nMain.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Copyright 2018 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { IntlShape } from 'react-intl';
|
||||
import { createIntl, createIntlCache } from 'react-intl';
|
||||
import type { LocaleMessageType, LocaleMessagesType } from '../types/I18N';
|
||||
import type {
|
||||
LocalizerType,
|
||||
ICUStringMessageParamsByKeyType,
|
||||
} from '../types/Util';
|
||||
import { strictAssert } from './assert';
|
||||
import * as log from '../logging/log';
|
||||
import * as Errors from '../types/errors';
|
||||
import { Environment, getEnvironment } from '../environment';
|
||||
import { bidiIsolate } from './unicodeBidi';
|
||||
|
||||
export function isLocaleMessageType(
|
||||
value: unknown
|
||||
): value is LocaleMessageType {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value != null &&
|
||||
Object.hasOwn(value, 'messageformat')
|
||||
);
|
||||
}
|
||||
|
||||
export type SetupI18nOptionsType = Readonly<{
|
||||
renderEmojify: (parts: ReadonlyArray<unknown>) => JSX.Element | void;
|
||||
}>;
|
||||
|
||||
export function createCachedIntl(
|
||||
locale: string,
|
||||
icuMessages: Record<string, string>,
|
||||
{ renderEmojify }: SetupI18nOptionsType
|
||||
): IntlShape {
|
||||
const intlCache = createIntlCache();
|
||||
const intl = createIntl(
|
||||
{
|
||||
locale: locale.replace('_', '-'), // normalize supported locales to browser format
|
||||
messages: icuMessages,
|
||||
defaultRichTextElements: {
|
||||
emojify: renderEmojify,
|
||||
},
|
||||
onError(error) {
|
||||
log.error('intl.onError', Errors.toLogFormat(error));
|
||||
},
|
||||
onWarn(warning) {
|
||||
if (
|
||||
getEnvironment() === Environment.Test &&
|
||||
warning.includes(
|
||||
// This warning is very noisy during tests
|
||||
'"defaultRichTextElements" was specified but "message" was not pre-compiled.'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
log.warn('intl.onWarn', warning);
|
||||
},
|
||||
},
|
||||
intlCache
|
||||
);
|
||||
return intl;
|
||||
}
|
||||
|
||||
function normalizeSubstitutions<
|
||||
Substitutions extends Record<string, string | number | Date> | undefined
|
||||
>(substitutions?: Substitutions): Substitutions | undefined {
|
||||
if (!substitutions) {
|
||||
return;
|
||||
}
|
||||
const normalized: Record<string, string | number | Date> = {};
|
||||
const entries = Object.entries(substitutions);
|
||||
if (entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const [key, value] of entries) {
|
||||
if (typeof value === 'string') {
|
||||
normalized[key] = bidiIsolate(value);
|
||||
} else {
|
||||
normalized[key] = value;
|
||||
}
|
||||
}
|
||||
return normalized as Substitutions;
|
||||
}
|
||||
|
||||
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 setupI18n(
|
||||
locale: string,
|
||||
messages: LocaleMessagesType,
|
||||
{ renderEmojify }: SetupI18nOptionsType
|
||||
): 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), {
|
||||
renderEmojify,
|
||||
});
|
||||
|
||||
const localizer: LocalizerType = (<
|
||||
Key extends keyof ICUStringMessageParamsByKeyType
|
||||
>(
|
||||
key: Key,
|
||||
substitutions: ICUStringMessageParamsByKeyType[Key]
|
||||
) => {
|
||||
const result = intl.formatMessage(
|
||||
{ id: key },
|
||||
normalizeSubstitutions(substitutions)
|
||||
);
|
||||
|
||||
strictAssert(result !== key, `i18n: missing translation for "${key}"`);
|
||||
|
||||
return result;
|
||||
}) as LocalizerType;
|
||||
|
||||
localizer.getIntl = () => {
|
||||
return intl;
|
||||
};
|
||||
localizer.getLocale = () => locale;
|
||||
localizer.getLocaleMessages = () => messages;
|
||||
localizer.getLocaleDirection = () => {
|
||||
return window.SignalContext.getResolvedMessagesLocaleDirection();
|
||||
};
|
||||
localizer.getHourCyclePreference = () => {
|
||||
return window.SignalContext.getHourCyclePreference();
|
||||
};
|
||||
|
||||
return localizer;
|
||||
}
|
Loading…
Reference in a new issue