fix: fix up string unit tests

pull/3281/head
Audric Ackermann 4 months ago
parent 62a3d4e9be
commit 6616ac4aa7
No known key found for this signature in database

@ -1,8 +1,8 @@
import styled from 'styled-components';
import type { LocalizerComponentProps } from '../../types/localizer';
import { SessionHtmlRenderer } from './SessionHTMLRenderer';
import {
GetMessageArgs,
LocalizerComponentProps,
MergedLocalizerTokens,
sanitizeArgs,
} from '../../localization/localeTools';

@ -1,10 +1,11 @@
import DOMPurify from 'dompurify';
import { createElement, type ElementType } from 'react';
import { createElement } from 'react';
import { supportedFormattingTags } from './Localizer';
import { LocalizerHtmlTag } from '../../localization/localeTools';
type ReceivedProps = {
html: string;
tag?: ElementType;
tag?: LocalizerHtmlTag;
key?: any;
className?: string;
};

@ -1,7 +1,7 @@
import styled from 'styled-components';
import { forwardRef } from 'react';
import { Localizer } from './Localizer';
import type { LocalizerComponentPropsObject } from '../../types/localizer';
import { LocalizerComponentPropsObject } from '../../localization/localeTools';
const StyledI18nSubTextContainer = styled('div')`
font-size: var(--font-size-md);

@ -17,7 +17,6 @@ import { ReleasedFeatures } from '../../util/releaseFeature';
import { Flex } from '../basic/Flex';
import { SpacerMD, TextWithChildren } from '../basic/Text';
import { ExpirableReadableMessage } from './message/message-item/ExpirableReadableMessage';
import type { LocalizerComponentPropsObject } from '../../types/localizer';
// eslint-disable-next-line import/order
import { ConversationInteraction } from '../../interactions';
@ -27,6 +26,7 @@ import { Localizer } from '../basic/Localizer';
import { SessionButtonColor } from '../basic/SessionButton';
import { SessionIcon } from '../icon';
import { getTimerNotificationStr } from '../../models/timerNotifications';
import { LocalizerComponentPropsObject } from '../../localization/localeTools';
const FollowSettingButton = styled.button`
color: var(--primary-color);

@ -16,10 +16,10 @@ import {
useSelectedIsGroupV2,
useSelectedNicknameOrProfileNameOrShortenedPubkey,
} from '../../../../state/selectors/selectedConversation';
import type { LocalizerComponentPropsObject } from '../../../../types/localizer';
import { Localizer } from '../../../basic/Localizer';
import { ExpirableReadableMessage } from './ExpirableReadableMessage';
import { NotificationBubble } from './notification-bubble/NotificationBubble';
import { LocalizerComponentPropsObject } from '../../../../localization/localeTools';
// This component is used to display group updates in the conversation view.

@ -45,7 +45,11 @@ export const CallNotification = (props: PropsForCallNotification) => {
isControlMessage={true}
>
<NotificationBubble iconType={iconType} iconColor={iconColor}>
<Localizer token={notificationTextKey} args={{ name }} />
{notificationTextKey === 'callsInProgress' ? (
<Localizer token={notificationTextKey} />
) : (
<Localizer token={notificationTextKey} args={{ name }} />
)}
</NotificationBubble>
</ExpirableReadableMessage>
);

@ -5,7 +5,7 @@ import { PubKey } from '../../../../session/types/PubKey';
import { Localizer } from '../../../basic/Localizer';
import { nativeEmojiData } from '../../../../util/emoji';
import type { LocalizerComponentPropsObject } from '../../../../types/localizer';
import { LocalizerComponentPropsObject } from '../../../../localization/localeTools';
export type TipPosition = 'center' | 'left' | 'right';

@ -10,9 +10,9 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/S
import { SessionRadioGroup, SessionRadioItems } from '../basic/SessionRadioGroup';
import { SpacerLG } from '../basic/Text';
import { SessionSpinner } from '../loading';
import type { LocalizerComponentPropsObject } from '../../types/localizer';
import { StyledI18nSubText } from '../basic/StyledI18nSubText';
import { LocalizerComponentPropsObject } from '../../localization/localeTools';
export interface SessionConfirmDialogProps {
i18nMessage?: LocalizerComponentPropsObject;

@ -13,7 +13,7 @@ import { Localizer } from '../../basic/Localizer';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../../basic/SessionButton';
import { StyledModalDescriptionContainer } from '../shared/ModalDescriptionContainer';
import { BlockOrUnblockModalState } from './BlockOrUnblockModalState';
import type { LocalizerComponentPropsObject } from '../../../types/localizer';
import { LocalizerComponentPropsObject } from '../../../localization/localeTools';
type ModalState = NonNullable<BlockOrUnblockModalState>;

@ -17,7 +17,7 @@ import { deleteDbLocally } from '../../../util/accountManager';
import { Flex } from '../../basic/Flex';
import { SessionButtonColor } from '../../basic/SessionButton';
import { SessionIconButton } from '../../icon';
import type { LocalizerComponentPropsObject } from '../../../types/localizer';
import { LocalizerComponentPropsObject } from '../../../localization/localeTools';
/** Min height should match the onboarding step with the largest height this prevents the loading spinner from jumping around while still keeping things centered */
const StyledBackButtonContainer = styled(Flex)`

@ -50,7 +50,6 @@ import { Storage, setLastProfileUpdateTimestamp } from '../util/storage';
import { UserGroupsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface';
import { ConversationInteractionStatus, ConversationInteractionType } from './types';
import { BlockedNumberController } from '../util';
import type { LocalizerComponentPropsObject } from '../types/localizer';
import { sendInviteResponseToGroup } from '../session/sending/group/GroupInviteResponse';
import { NetworkTime } from '../util/NetworkTime';
import { ClosedGroup } from '../session';
@ -59,6 +58,7 @@ import { GroupPromote } from '../session/utils/job_runners/jobs/GroupPromoteJob'
import { MessageSender } from '../session/sending';
import { StoreGroupRequestFactory } from '../session/apis/snode_api/factories/StoreGroupRequestFactory';
import { DURATION } from '../session/constants';
import { LocalizerComponentPropsObject } from '../localization/localeTools';
export async function copyPublicKeyByConvoId(convoId: string) {
if (OpenGroupUtils.isOpenGroupV2(convoId)) {

@ -109,6 +109,14 @@ type MappedToTsTypes<T extends Record<string, DynamicArgStr>> = {
[K in keyof T]: ArgsTypeStrToTypes<T[K]>;
};
function propsToTuple<T extends MergedLocalizerTokens>(
opts: LocalizerComponentProps<T>
): GetMessageArgs<T> {
return (
isTokenWithArgs(opts.token) ? [opts.token, opts.args] : [opts.token]
) as GetMessageArgs<T>;
}
/** NOTE: Because of docstring limitations changes MUST be manually synced between {@link setupI18n.inEnglish } and {@link window.i18n.inEnglish } */
/**
* Retrieves a message string in the {@link en} locale, substituting variables where necessary.
@ -178,6 +186,12 @@ export function stripped<T extends MergedLocalizerTokens>(
return deSanitizeHtmlTags(strippedString, '\u200B');
}
export function strippedWithObj<T extends MergedLocalizerTokens>(
opts: LocalizerComponentProps<T>
): string {
return stripped(...propsToTuple(opts));
}
/**
* Sanitizes the args to be used in the i18n function
* @param args The args to sanitize
@ -489,6 +503,26 @@ export function localize<T extends MergedLocalizerTokens>(token: T) {
return new LocalizedStringBuilder<T>(token, localeInUse);
}
function localizeFromOld<T extends MergedLocalizerTokens>(token: T, args: ArgsFromToken<T>) {
export function localizeFromOld<T extends MergedLocalizerTokens>(token: T, args: ArgsFromToken<T>) {
return localize(token).withArgs(args);
}
export type LocalizerHtmlTag = 'span' | 'div';
/** Basic props for all calls of the Localizer component */
type LocalizerComponentBaseProps<T extends MergedLocalizerTokens> = {
token: T;
asTag?: LocalizerHtmlTag;
className?: string;
};
/** The props for the localization component */
export type LocalizerComponentProps<T extends MergedLocalizerTokens> =
T extends MergedLocalizerTokens
? ArgsFromToken<T> extends never
? LocalizerComponentBaseProps<T> & { args?: undefined }
: ArgsFromToken<T> extends Record<string, never>
? LocalizerComponentBaseProps<T> & { args?: undefined }
: LocalizerComponentBaseProps<T> & { args: ArgsFromToken<T> }
: never;
export type LocalizerComponentPropsObject = LocalizerComponentProps<MergedLocalizerTokens>;

@ -1,6 +1,6 @@
import { LocalizerComponentPropsObject } from '../localization/localeTools';
import { ConvoHub } from '../session/conversations';
import { UserUtils } from '../session/utils';
import type { LocalizerComponentPropsObject } from '../types/localizer';
function usAndXOthers(arr: Array<string>) {
const us = UserUtils.getOurPubKeyStrFromCache();
@ -23,7 +23,7 @@ export function getKickedGroupUpdateStr(
if (us) {
switch (others.length) {
case 0:
return { token: 'groupRemovedYouGeneral' };
return { token: 'groupRemovedYouGeneral', args: undefined };
case 1:
return { token: 'groupRemovedYouTwo', args: { other_name: othersNames[0] } };
default:
@ -33,7 +33,7 @@ export function getKickedGroupUpdateStr(
switch (othersNames.length) {
case 0:
return { token: 'groupUpdated' };
return { token: 'groupUpdated', args: undefined };
case 1:
return { token: 'groupRemoved', args: { name: othersNames[0] } };
case 2:
@ -63,7 +63,7 @@ export function getLeftGroupUpdateChangeStr(left: Array<string>): LocalizerCompo
}
return us
? { token: 'groupMemberYouLeft' }
? { token: 'groupMemberYouLeft', args: undefined }
: {
token: 'groupMemberLeft',
args: {
@ -85,7 +85,10 @@ export function getJoinedGroupUpdateChangeStr(
if (us) {
switch (othersNames.length) {
case 0:
return { token: addedWithHistory ? 'groupInviteYouHistory' : 'groupInviteYou' };
return {
token: addedWithHistory ? 'groupInviteYouHistory' : 'groupInviteYou',
args: undefined,
};
case 1:
return addedWithHistory
? { token: 'groupMemberNewYouHistoryTwo', args: { other_name: othersNames[0] } }
@ -98,7 +101,7 @@ export function getJoinedGroupUpdateChangeStr(
}
switch (othersNames.length) {
case 0:
return { token: 'groupUpdated' }; // this is an invalid case, but well.
return { token: 'groupUpdated', args: undefined }; // this is an invalid case, but well.
case 1:
return addedWithHistory
? { token: 'groupMemberNewHistory', args: { name: othersNames[0] } }
@ -130,7 +133,7 @@ export function getJoinedGroupUpdateChangeStr(
if (us) {
switch (othersNames.length) {
case 0:
return { token: 'legacyGroupMemberYouNew' };
return { token: 'legacyGroupMemberYouNew', args: undefined };
case 1:
return { token: 'legacyGroupMemberNewYouOther', args: { other_name: othersNames[0] } };
default:
@ -139,7 +142,7 @@ export function getJoinedGroupUpdateChangeStr(
}
switch (othersNames.length) {
case 0:
return { token: 'groupUpdated' };
return { token: 'groupUpdated', args: undefined };
case 1:
return { token: 'legacyGroupMemberNew', args: { name: othersNames[0] } };
case 2:
@ -170,7 +173,7 @@ export function getPromotedGroupUpdateChangeStr(
if (us) {
switch (othersNames.length) {
case 0:
return { token: 'groupPromotedYou' };
return { token: 'groupPromotedYou', args: undefined };
case 1:
return { token: 'groupPromotedYouTwo', args: { name: othersNames[0] } };
default:
@ -179,7 +182,7 @@ export function getPromotedGroupUpdateChangeStr(
}
switch (othersNames.length) {
case 0:
return { token: 'groupUpdated' };
return { token: 'groupUpdated', args: undefined };
case 1:
return { token: 'adminPromotedToAdmin', args: { name: othersNames[0] } };
case 2:
@ -204,9 +207,9 @@ export function getPromotedGroupUpdateChangeStr(
export function getGroupNameChangeStr(newName: string | undefined): LocalizerComponentPropsObject {
return newName
? { token: 'groupNameNew', args: { group_name: newName } }
: { token: 'groupNameUpdated' };
: { token: 'groupNameUpdated', args: undefined };
}
export function getGroupDisplayPictureChangeStr(): LocalizerComponentPropsObject {
return { token: 'groupDisplayPictureUpdated' };
return { token: 'groupDisplayPictureUpdated', args: undefined };
}

@ -269,61 +269,39 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
this.getConversation()?.getNicknameOrRealUsernameOrPlaceholder() || window.i18n('unknown');
if (groupUpdate.left) {
// @ts-expect-error -- TODO: Fix by using new i18n builder
const { token, args } = getLeftGroupUpdateChangeStr(groupUpdate.left, groupName);
// TODO: clean up this typing
return window.i18n.stripped(...[token, args]);
return window.i18n.strippedWithObj(getLeftGroupUpdateChangeStr(groupUpdate.left));
}
if (groupUpdate.name) {
const result = getGroupNameChangeStr(groupUpdate.name);
if ('args' in result) {
return window.i18n.stripped(...[result.token, result.args]);
}
return window.i18n.stripped(...[result.token]);
return window.i18n.strippedWithObj(getGroupNameChangeStr(groupUpdate.name));
}
if (groupUpdate.avatarChange) {
const result = getGroupDisplayPictureChangeStr();
return window.i18n.stripped(...[result.token]);
return window.i18n.strippedWithObj(getGroupDisplayPictureChangeStr());
}
if (groupUpdate.joined?.length) {
// @ts-expect-error -- TODO: Fix by using new i18n builder
const { token, args } = getJoinedGroupUpdateChangeStr(
groupUpdate.joined,
isGroupV2,
false,
groupName
);
// TODO: clean up this typing
return window.i18n.stripped(...[token, args]);
const opts = getJoinedGroupUpdateChangeStr(groupUpdate.joined, isGroupV2, false, groupName);
return window.i18n.strippedWithObj(opts);
}
if (groupUpdate.joinedWithHistory?.length) {
// @ts-expect-error -- TODO: Fix by using new i18n builder
const { token, args } = getJoinedGroupUpdateChangeStr(
const opts = getJoinedGroupUpdateChangeStr(
groupUpdate.joinedWithHistory,
true,
true,
groupName
);
// TODO: clean up this typing
return window.i18n.stripped(...[token, args]);
return window.i18n.strippedWithObj(opts);
}
if (groupUpdate.kicked?.length) {
// @ts-expect-error -- TODO: Fix by using new i18n builder
const { token, args } = getKickedGroupUpdateStr(groupUpdate.kicked, groupName);
// TODO: clean up this typing
return window.i18n.stripped(...[token, args]);
const opts = getKickedGroupUpdateStr(groupUpdate.kicked, groupName);
return window.i18n.strippedWithObj(opts);
}
if (groupUpdate.promoted?.length) {
// @ts-expect-error -- TODO: Fix by using new i18n builder
const { token, args } = getPromotedGroupUpdateChangeStr(groupUpdate.promoted, groupName);
// TODO: clean up this typing
return window.i18n.stripped(...[token, args]);
const opts = getPromotedGroupUpdateChangeStr(groupUpdate.promoted);
return window.i18n.strippedWithObj(opts);
}
window.log.warn('did not build a specific change for getDescription of ', groupUpdate);
@ -429,10 +407,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
timespanSeconds: expireTimer,
});
if ('args' in i18nProps) {
return window.i18n.stripped(...[i18nProps.token, i18nProps.args]);
}
return window.i18n.stripped(...[i18nProps.token]);
return window.i18n.strippedWithObj(i18nProps);
}
const body = this.get('body');
if (body) {

@ -5,7 +5,7 @@ import { PubKey } from '../session/types';
import { UserUtils } from '../session/utils';
import { TimerOptions } from '../session/disappearing_messages/timerOptions';
import { isLegacyDisappearingModeEnabled } from '../session/disappearing_messages/legacy';
import type { LocalizerComponentPropsObject } from '../types/localizer';
import { LocalizerComponentPropsObject } from '../localization/localeTools';
export function getTimerNotificationStr({
expirationMode,

@ -10,7 +10,6 @@ import {
} from '../../../../util/accountManager';
import { TestUtils } from '../../../test-utils';
import { stubWindow } from '../../../test-utils/utils';
import { resetLocaleAndTranslationDict } from '../../../../util/i18n/shared';
describe('Onboarding', () => {
const polledDisplayName = 'Hello World';
@ -26,7 +25,6 @@ describe('Onboarding', () => {
});
afterEach(() => {
resetLocaleAndTranslationDict();
Sinon.restore();
});

@ -8,7 +8,6 @@ import {
_getSortedConversations,
} from '../../../../state/selectors/conversations';
import { TestUtils } from '../../../test-utils';
import { resetLocaleAndTranslationDict } from '../../../../util/i18n/shared';
describe('state/selectors/conversations', () => {
beforeEach(() => {
@ -16,7 +15,6 @@ describe('state/selectors/conversations', () => {
TestUtils.stubI18n();
});
afterEach(() => {
resetLocaleAndTranslationDict();
Sinon.restore();
});
describe('#getSortedConversationsList', () => {

@ -2,16 +2,12 @@
// @ts-nocheck - TODO: add generic type to setupI18n to fix this
import { expect } from 'chai';
import { initI18n, testDictionary } from './util';
import { resetLocaleAndTranslationDict } from '../../../../../util/i18n/shared';
import { initI18n } from './util';
describe('formatMessageWithArgs', () => {
let i18n;
beforeEach(() => {
i18n = initI18n(testDictionary);
});
afterEach(() => {
resetLocaleAndTranslationDict();
i18n = initI18n();
});
it('returns the message with args for a message', () => {

@ -2,46 +2,26 @@
// @ts-nocheck - TODO: add generic type to setupI18n to fix this
import { expect } from 'chai';
import { initI18n, testDictionary } from './util';
import { resetLocaleAndTranslationDict } from '../../../../../util/i18n/shared';
import { initI18n } from './util';
describe('getMessage', () => {
let i18n;
beforeEach(() => {
i18n = initI18n(testDictionary);
});
afterEach(() => {
resetLocaleAndTranslationDict();
});
it('returns the message for a token', () => {
const message = i18n('greeting', { name: 'Alice' });
expect(message).to.equal('Hello, Alice!');
const message = initI18n()('searchContacts');
expect(message).to.equal('Search Contacts');
});
it('returns the message for a plural token', () => {
const message = i18n('search', { count: 1, found_count: 2 });
const message = initI18n()('searchMatches', { count: 1, found_count: 2 });
expect(message).to.equal('2 of 1 match');
});
it('returns the message for a token with no args', () => {
const message = i18n('noArgs');
expect(message).to.equal('No args');
});
it('returns the message for a token with args', () => {
const message = i18n('args', { name: 'Alice' });
expect(message).to.equal('Hello, Alice!');
});
it('returns the message for a token with a tag', () => {
const message = i18n('tag', { name: 'Alice' });
expect(message).to.equal('Hello, Alice! <b>Welcome!</b>');
const message = initI18n()('adminPromote');
expect(message).to.equal('Promote Admins');
});
it('returns the message for a token with a tag and args', () => {
const message = i18n('argInTag', { name: 'Alice', arg: 'Bob' });
expect(message).to.equal('Hello, Alice! <b>Welcome, Bob!</b>');
const message = initI18n()('adminPromotedToAdmin', { name: 'Alice' });
expect(message).to.equal('<b>Alice</b> was promoted to Admin.');
});
});

@ -2,46 +2,49 @@
// @ts-nocheck - TODO: add generic type to setupI18n to fix this
import { expect } from 'chai';
import { initI18n, testDictionary } from './util';
import { resetLocaleAndTranslationDict } from '../../../../../util/i18n/shared';
import { initI18n } from './util';
describe('getRawMessage', () => {
let i18n;
beforeEach(() => {
i18n = initI18n(testDictionary);
});
afterEach(() => {
resetLocaleAndTranslationDict();
});
it('returns the raw message for a token', () => {
const rawMessage = i18n.getRawMessage('greeting', { name: 'Alice' });
expect(rawMessage).to.equal('Hello, {name}!');
const rawMessage = initI18n().getRawMessage('en', 'adminPromoteDescription', { name: 'Alice' });
expect(rawMessage).to.equal(
'Are you sure you want to promote <b>{name}</b> to admin? Admins cannot be removed.'
);
});
it('returns the raw message for a plural token', () => {
const rawMessage = i18n.getRawMessage('search', { count: 1, found_count: 2 });
const rawMessage = initI18n().getRawMessage('en', 'searchMatches', {
count: 1,
found_count: 2,
});
expect(rawMessage).to.equal('{found_count} of {count} match');
});
it('returns the raw message for a token with no args', () => {
const rawMessage = i18n.getRawMessage('noArgs');
expect(rawMessage).to.equal('No args');
const rawMessage = initI18n().getRawMessage('en', 'adminCannotBeRemoved');
expect(rawMessage).to.equal('Admins cannot be removed.');
});
it('returns the raw message for a token with args', () => {
const rawMessage = i18n.getRawMessage('args', { name: 'Alice' });
expect(rawMessage).to.equal('Hello, {name}!');
const rawMessage = initI18n().getRawMessage('en', 'adminPromotionFailedDescription', {
name: 'Alice',
group_name: 'Group',
});
expect(rawMessage).to.equal('Failed to promote {name} in {group_name}');
});
it('returns the raw message for a token with a tag', () => {
const rawMessage = i18n.getRawMessage('tag', { name: 'Alice' });
expect(rawMessage).to.equal('Hello, {name}! <b>Welcome!</b>');
const message = initI18n().getRawMessage('en', 'screenshotTaken', { name: 'Alice' });
expect(message).to.equal('<b>{name}</b> took a screenshot.');
});
it('returns the raw message for a token with a tag and args', () => {
const rawMessage = i18n.getRawMessage('argInTag', { name: 'Alice', arg: 'Bob' });
expect(rawMessage).to.equal('Hello, {name}! <b>Welcome, {arg}!</b>');
const message = initI18n().getRawMessage('en', 'adminPromoteTwoDescription', {
name: 'Alice',
other_name: 'Bob',
});
expect(message).to.equal(
'Are you sure you want to promote <b>{name}</b> and <b>{other_name}</b> to admin? Admins cannot be removed.'
);
});
});

@ -1,11 +1,7 @@
import { expect } from 'chai';
import { initI18n } from './util';
import { resetLocaleAndTranslationDict } from '../../../../../util/i18n/shared';
describe('setupI18n', () => {
afterEach(() => {
resetLocaleAndTranslationDict();
});
it('returns setupI18n with all methods defined', () => {
const setupI18nReturn = initI18n();
expect(setupI18nReturn).to.be.a('function');

@ -2,45 +2,31 @@
// @ts-nocheck - TODO: add generic type to setupI18n to fix this
import { expect } from 'chai';
import { initI18n, testDictionary } from './util';
import { resetLocaleAndTranslationDict } from '../../../../../util/i18n/shared';
import { initI18n } from './util';
describe('stripped', () => {
let i18n;
beforeEach(() => {
i18n = initI18n(testDictionary);
});
afterEach(() => {
resetLocaleAndTranslationDict();
});
it('returns the stripped message for a token', () => {
const message = i18n.stripped('greeting', { name: 'Alice' });
expect(message).to.equal('Hello, Alice!');
const message = initI18n().stripped('search');
expect(message).to.equal('Search');
});
it('returns the stripped message for a plural token', () => {
const message = i18n.stripped('search', { count: 1, found_count: 2 });
const message = initI18n().stripped('searchMatches', { count: 1, found_count: 2 });
expect(message).to.equal('2 of 1 match');
});
it('returns the stripped message for a token with no args', () => {
const message = i18n.stripped('noArgs');
expect(message).to.equal('No args');
});
it('returns the stripped message for a token with args', () => {
const message = i18n.stripped('args', { name: 'Alice' });
expect(message).to.equal('Hello, Alice!');
});
it('returns the stripped message for a token with the tags stripped', () => {
const message = i18n.stripped('tag', { name: 'Alice' });
expect(message).to.equal('Hello, Alice! Welcome!');
const message = initI18n().stripped('messageRequestYouHaveAccepted', { name: 'Alice' });
expect(message).to.equal('You have accepted the message request from Alice.');
});
it('returns the stripped message for a token with the tags stripped', () => {
const message = i18n.stripped('argInTag', { name: 'Alice', arg: 'Bob' });
expect(message).to.equal('Hello, Alice! Welcome, Bob!');
const message = initI18n().stripped('adminPromoteTwoDescription', {
name: 'Alice',
other_name: 'Bob',
});
expect(message).to.equal(
'Are you sure you want to promote Alice and Bob to admin? Admins cannot be removed.'
);
});
});

@ -1,17 +1,7 @@
import { setupI18n } from '../../../../../util/i18n/i18n';
export const testDictionary = {
greeting: 'Hello, {name}!',
search: '{found_count} of {count} match',
noArgs: 'No args',
args: 'Hello, {name}!',
tag: 'Hello, {name}! <b>Welcome!</b>',
argInTag: 'Hello, {name}! <b>Welcome, {arg}!</b>',
} as const;
export function initI18n() {
return setupI18n({
// testing
crowdinLocale: 'en',
crowdinLocale: 'en', // testing
});
}

@ -1,45 +1,29 @@
import type { ElementType } from 'react';
import type { ArgsFromToken, MergedLocalizerTokens } from '../localization/localeTools';
import type {
ArgsFromToken,
MergedLocalizerTokens,
GetMessageArgs,
LocalizerComponentProps,
} from '../localization/localeTools';
import { CrowdinLocale } from '../localization/constants';
/** Basic props for all calls of the Localizer component */
type LocalizerComponentBaseProps<T extends MergedLocalizerTokens> = {
token: T;
asTag?: ElementType;
className?: string;
};
/** The props for the localization component */
export type LocalizerComponentProps<T extends MergedLocalizerTokens> =
T extends MergedLocalizerTokens
? ArgsFromToken<T> extends never
? LocalizerComponentBaseProps<T>
: ArgsFromToken<T> extends Record<string, never>
? LocalizerComponentBaseProps<T>
: LocalizerComponentBaseProps<T> & { args: ArgsFromToken<T> }
: never;
export type LocalizerComponentPropsObject = LocalizerComponentProps<MergedLocalizerTokens>;
export type I18nMethods = {
/** @see {@link window.i18n.stripped} */
stripped: <T extends MergedLocalizerTokens, R extends LocalizerDictionary[T]>(
...[token, args]: GetMessageArgs<T>
) => R | T;
stripped: <T extends MergedLocalizerTokens>(...[token, args]: GetMessageArgs<T>) => string | T;
strippedWithObj: <T extends MergedLocalizerTokens>(
opts: LocalizerComponentProps<T>
) => string | T;
/** @see {@link window.i18n.inEnglish} */
inEnglish: <T extends MergedLocalizerTokens, R extends LocalizerDictionary[T]>(
...[token, args]: GetMessageArgs<T>
) => R | T;
inEnglish: <T extends MergedLocalizerTokens>(...[token, args]: GetMessageArgs<T>) => string | T;
/** @see {@link window.i18n.formatMessageWithArgs */
getRawMessage: <T extends MergedLocalizerTokens>(
crowdinLocale: CrowdinLocale,
...[token, args]: GetMessageArgs<T>
) => string;
) => string | T;
/** @see {@link window.i18n.formatMessageWithArgs} */
formatMessageWithArgs: <T extends MergedLocalizerTokens>(
rawMessage: string,
args?: ArgsFromToken<T>
) => string;
) => string | T;
};
export type SetupI18nReturnType = I18nMethods &

@ -7,12 +7,14 @@ import {
inEnglish,
stripped,
getMessageDefault,
strippedWithObj,
} from '../../../localization/localeTools';
const getMessageDefaultCopy: any = getMessageDefault;
getMessageDefaultCopy.inEnglish = inEnglish;
getMessageDefaultCopy.stripped = stripped;
getMessageDefaultCopy.strippedWithObj = strippedWithObj;
getMessageDefaultCopy.getRawMessage = getRawMessage;
getMessageDefaultCopy.formatMessageWithArgs = formatMessageWithArgs;

@ -5,13 +5,6 @@ import { timeLocaleMap } from './timeLocaleMap';
let mappedBrowserLocaleDisplayed = false;
let crowdinLocale: CrowdinLocale | undefined;
/**
* Only exported for testing, reset the dictionary to use for translations and the locale set
*/
export function resetLocaleAndTranslationDict() {
crowdinLocale = undefined;
}
/**
* Logs an i18n message to the console.
* @param message - The message to log.
@ -88,7 +81,7 @@ export function getBrowserLocale() {
export function setInitialLocale(crowdinLocaleArg: CrowdinLocale) {
if (crowdinLocale) {
throw new Error('setInitialLocale: crowdinLocale is already init');
i18nLog('setInitialLocale: crowdinLocale is already init');
}
crowdinLocale = crowdinLocaleArg;
}

2
ts/window.d.ts vendored

@ -78,6 +78,8 @@ declare global {
*/
stripped: I18nMethods['stripped'];
strippedWithObj: I18nMethods['strippedWithObj'];
/** NOTE: Because of docstring limitations changes MUST be manually synced between {@link setupI18n.inEnglish } and {@link window.i18n.inEnglish } */
/**
* Retrieves a message string in the {@link en} locale, substituting variables where necessary.

Loading…
Cancel
Save