From 80644817398b0c5cdc5dc22cb6026fad607696d9 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Fri, 16 Feb 2024 10:48:51 +1100 Subject: [PATCH 01/93] fix: add LocalizerKeys type to getMessage and remove updateLocale function --- ts/node/global_errors.ts | 10 ++-------- ts/util/i18n.ts | 3 ++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/ts/node/global_errors.ts b/ts/node/global_errors.ts index b0d8713aff..ef7c75eff3 100644 --- a/ts/node/global_errors.ts +++ b/ts/node/global_errors.ts @@ -1,11 +1,10 @@ import { app, clipboard, dialog } from 'electron'; import { redactAll } from '../util/privacy'; // checked - only node -import { LocaleMessagesType } from './locale'; // checked - only node import { ConsoleCustom } from './logging'; // checked - only node // We use hard-coded strings until we're able to update these strings from the locale. -let quitText = 'Quit'; -let copyErrorAndQuitText = 'Copy error to clipboard'; +const quitText = 'Quit'; +const copyErrorAndQuitText = 'Copy error to clipboard'; async function handleError(prefix: string, error: any) { if ((console as ConsoleCustom)._error) { @@ -33,11 +32,6 @@ async function handleError(prefix: string, error: any) { } } -export const updateLocale = (messages: LocaleMessagesType) => { - quitText = messages.quit; - copyErrorAndQuitText = messages.copyErrorAndQuit; -}; - export const setupGlobalErrorHandler = () => { // eslint-disable-next-line @typescript-eslint/no-misused-promises process.on('uncaughtException', async error => { diff --git a/ts/util/i18n.ts b/ts/util/i18n.ts index 7701bc06d2..732fb25800 100644 --- a/ts/util/i18n.ts +++ b/ts/util/i18n.ts @@ -1,6 +1,7 @@ // this file is a weird one as it is used by both sides of electron at the same time import { LocaleMessagesType } from '../node/locale'; +import { LocalizerKeys } from '../types/LocalizerKeys'; export const setupi18n = (locale: string, messages: LocaleMessagesType) => { if (!locale) { @@ -10,7 +11,7 @@ export const setupi18n = (locale: string, messages: LocaleMessagesType) => { throw new Error('i18n: messages parameter is required'); } - function getMessage(key: string, substitutions: Array) { + function getMessage(key: LocalizerKeys, substitutions: Array) { const message = messages[key]; if (!message) { // eslint:disable: no-console From 28d055ce40d5b176ab843926c7608d21ee300993 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Mon, 19 Feb 2024 14:49:31 +1100 Subject: [PATCH 02/93] fix: add withAcceleratorPrefix function to remove accelerator prefixes from locale strings --- _locales/en/messages.json | 13 +++++-------- ts/node/menu.ts | 21 ++++++++++++++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 176161e2c3..277008eae2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2,14 +2,11 @@ "copyErrorAndQuit": "Copy error and quit", "unknown": "Unknown", "databaseError": "Database Error", - "mainMenuFile": "&File", - "mainMenuEdit": "&Edit", - "mainMenuView": "&View", - "mainMenuWindow": "&Window", - "mainMenuHelp": "&Help", - "appMenuHide": "Hide", - "appMenuHideOthers": "Hide Others", - "appMenuUnhide": "Show All", + "file": "File", + "edit": "Edit", + "view": "View", + "window": "Window", + "sessionHelp": "Help", "appMenuQuit": "Quit Session", "editMenuUndo": "Undo", "editMenuRedo": "Redo", diff --git a/ts/node/menu.ts b/ts/node/menu.ts index fb2862e1ba..a9516a4564 100644 --- a/ts/node/menu.ts +++ b/ts/node/menu.ts @@ -2,6 +2,17 @@ import { isString } from 'lodash'; import { LocaleMessagesType } from './locale'; import { Noop } from '../types/Util'; +/** + * Adds the accelerator prefix to the label for the menu item + * @link https://www.electronjs.org/docs/latest/api/menu#static-methods + * + * @param label - The label for the menu item + * @returns The label with the accelerator prefix + */ +const withAcceleratorPrefix = (label:string) => { + return `&${label}`; +} + export const createTemplate = ( options: { openReleaseNotes: () => void; @@ -28,7 +39,7 @@ export const createTemplate = ( const template = [ { - label: messages.mainMenuFile, + label: withAcceleratorPrefix(messages.file), submenu: [ { type: 'separator', @@ -40,7 +51,7 @@ export const createTemplate = ( ], }, { - label: messages.mainMenuEdit, + label: withAcceleratorPrefix(messages.edit), submenu: [ { role: 'undo', @@ -76,7 +87,7 @@ export const createTemplate = ( ], }, { - label: messages.mainMenuView, + label: withAcceleratorPrefix(messages.view), submenu: [ { role: 'resetzoom', @@ -115,7 +126,7 @@ export const createTemplate = ( ], }, { - label: messages.mainMenuWindow, + label: withAcceleratorPrefix(messages.window), role: 'window', submenu: [ { @@ -125,7 +136,7 @@ export const createTemplate = ( ], }, { - label: messages.mainMenuHelp, + label: withAcceleratorPrefix(messages.sessionHelp), role: 'help', submenu: [ { From 0a9535e9db95f3a1202bb663cb7fc916a2f66f2e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 1 Mar 2024 09:30:47 +1100 Subject: [PATCH 03/93] Update Crowdin configuration file --- crowdin.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000000..172b18ede4 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,2 @@ +bundles: + - 12 From a69e00aca1dc5542191e0a8f4792c1a18e74b942 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 1 Mar 2024 09:32:54 +1100 Subject: [PATCH 04/93] Update crowdin.yml --- crowdin.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crowdin.yml b/crowdin.yml index 172b18ede4..8b9efcc46d 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,2 +1,6 @@ +commit_message: "[CI SKIP]" +files: + - source: _locales/en/messages.json + translation: _locales/%locale_with_underscore%/messages.json bundles: - 12 From 4876a8188c1ab169f30feef358e3cf410f337510 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 1 Mar 2024 09:33:05 +1100 Subject: [PATCH 05/93] Update Crowdin configuration file --- crowdin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crowdin.yml b/crowdin.yml index 8b9efcc46d..c7cecaae61 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,4 +1,4 @@ -commit_message: "[CI SKIP]" +commit_message: '[CI SKIP]' files: - source: _locales/en/messages.json translation: _locales/%locale_with_underscore%/messages.json From 7c768279dfa85d89e5b35b718817b6f27a886da5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 1 Mar 2024 16:54:43 +1100 Subject: [PATCH 06/93] Update crowdin.yml [CI SKIP] --- crowdin.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/crowdin.yml b/crowdin.yml index c7cecaae61..e850f0be3b 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,6 +1,3 @@ commit_message: '[CI SKIP]' -files: - - source: _locales/en/messages.json - translation: _locales/%locale_with_underscore%/messages.json bundles: - 12 From 19442d6be69dfac84fcc79cf3b26ca44a66888d1 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Tue, 12 Mar 2024 17:06:44 +1100 Subject: [PATCH 07/93] chore: disappearing messages string changes --- .../conversation/TimerNotification.tsx | 62 ++++++++++++------- .../header/ConversationHeaderTitle.tsx | 4 +- .../message-content/MessageReactBar.tsx | 18 ++++-- .../message-content/quote/QuoteText.tsx | 4 +- .../overlay/OverlayRightPanelSettings.tsx | 12 ++-- .../OverlayDisappearingMessages.tsx | 4 +- ts/components/dialog/SessionConfirm.tsx | 6 +- ts/models/conversation.ts | 10 +-- ts/state/selectors/selectedConversation.ts | 14 ++--- 9 files changed, 79 insertions(+), 55 deletions(-) diff --git a/ts/components/conversation/TimerNotification.tsx b/ts/components/conversation/TimerNotification.tsx index 7e5150cd7f..e823cb8d02 100644 --- a/ts/components/conversation/TimerNotification.tsx +++ b/ts/components/conversation/TimerNotification.tsx @@ -47,7 +47,10 @@ function useFollowSettingsButtonClick( : window.i18n('timerModeSent'); const message = props.disabled ? window.i18n('followSettingDisabled') - : window.i18n('followSettingTimeAndType', [props.timespanText, mode]); + : window.i18n('followSettingTimeAndType', { + time: props.timespanText, + type: mode, + }); const okText = props.disabled ? window.i18n('confirm') : window.i18n('set'); dispatch( updateConfirmModal({ @@ -148,30 +151,43 @@ function useTextToRender(props: PropsForExpirationTimer) { : window.i18n('timerModeSent'); switch (type) { case 'fromOther': - return disabled - ? window.i18n( - ownSideOnly ? 'theyDisabledTheirDisappearingMessages' : 'disabledDisappearingMessages', - [contact, timespanText] - ) - : mode - ? window.i18n(ownSideOnly ? 'theySetTheirDisappearingMessages' : 'theyChangedTheTimer', [ - contact, - timespanText, - mode, - ]) - : window.i18n('theyChangedTheTimerLegacy', [contact, timespanText]); + if (disabled) { + return ownSideOnly + ? window.i18n('theyDisabledTheirDisappearingMessages', { name: contact }) + : window.i18n('disappearingMessagesTurnedOff', { name: contact }); + } + + if (mode) { + return ownSideOnly + ? window.i18n('theySetTheirDisappearingMessages', { + name: contact, + time: timespanText, + type: mode, + }) + : window.i18n('theyChangedTheTimer', { + name: contact, + time: timespanText, + // TODO: check this mode + mode: '', + }); + } + + return window.i18n('theyChangedTheTimerLegacy', { name: contact, time: timespanText }); + case 'fromMe': case 'fromSync': - return disabled - ? window.i18n( - ownSideOnly ? 'youDisabledYourDisappearingMessages' : 'youDisabledDisappearingMessages' - ) - : mode - ? window.i18n(ownSideOnly ? 'youSetYourDisappearingMessages' : 'youChangedTheTimer', [ - timespanText, - mode, - ]) - : window.i18n('youChangedTheTimerLegacy', [timespanText]); + if (disabled) { + return ownSideOnly + ? window.i18n('youDisabledYourDisappearingMessages') + : window.i18n('disappearingMessagesTurnedOff', { name: contact }); + } + if (mode) { + return ownSideOnly + ? window.i18n('youSetYourDisappearingMessages', { time: timespanText, type: mode }) + : window.i18n('youChangedTheTimer', { time: timespanText, mode }); + } + return window.i18n('youChangedTheTimerLegacy', { time: timespanText }); + default: assertUnreachable(type, `TimerNotification: Missing case error "${type}"`); } diff --git a/ts/components/conversation/header/ConversationHeaderTitle.tsx b/ts/components/conversation/header/ConversationHeaderTitle.tsx index d496b846ad..e8d56aa5b8 100644 --- a/ts/components/conversation/header/ConversationHeaderTitle.tsx +++ b/ts/components/conversation/header/ConversationHeaderTitle.tsx @@ -61,7 +61,7 @@ export const ConversationHeaderTitle = () => { const { i18n } = window; const notificationSubtitle = useMemo( - () => (notificationSetting ? i18n('notificationSubtitle', [notificationSetting]) : null), + () => (notificationSetting ? i18n('sessionNotifications') : null), [i18n, notificationSetting] ); @@ -77,7 +77,7 @@ export const ConversationHeaderTitle = () => { if (isGroup && memberCount > 0 && !isKickedFromGroup) { const count = String(memberCount); - return isPublic ? i18n('activeMembers', [count]) : i18n('members', [count]); + return i18n(isPublic ? 'activeMembers' : 'members', { count }); } return null; diff --git a/ts/components/conversation/message/message-content/MessageReactBar.tsx b/ts/components/conversation/message/message-content/MessageReactBar.tsx index 2d7d0cad61..b9170ae8f8 100644 --- a/ts/components/conversation/message/message-content/MessageReactBar.tsx +++ b/ts/components/conversation/message/message-content/MessageReactBar.tsx @@ -102,22 +102,30 @@ function formatTimeLeft({ timeLeftMs }: { timeLeftMs: number }) { } if (timeLeft.isBefore(moment.utc(0).add(1, 'minute'))) { - return window.i18n('messageWillDisappear', [`${timeLeft.seconds()}s`]); + return window.i18n('messageWillDisappear', { + countAndUnit: `${timeLeft.seconds()}s`, + }); } if (timeLeft.isBefore(moment.utc(0).add(1, 'hour'))) { const extraUnit = timeLeft.seconds() ? ` ${timeLeft.seconds()}s` : ''; - return window.i18n('messageWillDisappear', [`${timeLeft.minutes()}m${extraUnit}`]); + return window.i18n('messageWillDisappear', { + countAndUnit: `${timeLeft.minutes()}m${extraUnit}`, + }); } if (timeLeft.isBefore(moment.utc(0).add(1, 'day'))) { const extraUnit = timeLeft.minutes() ? ` ${timeLeft.minutes()}m` : ''; - return window.i18n('messageWillDisappear', [`${timeLeft.hours()}h${extraUnit}`]); + return window.i18n('messageWillDisappear', { + countAndUnit: `${timeLeft.hours()}h${extraUnit}`, + }); } if (timeLeft.isBefore(moment.utc(0).add(7, 'day'))) { const extraUnit = timeLeft.hours() ? ` ${timeLeft.hours()}h` : ''; - return window.i18n('messageWillDisappear', [`${timeLeft.dayOfYear() - 1}d${extraUnit}`]); + return window.i18n('messageWillDisappear', { + countAndUnit: `${timeLeft.dayOfYear() - 1}d${extraUnit}`, + }); } if (timeLeft.isBefore(moment.utc(0).add(31, 'day'))) { @@ -125,7 +133,7 @@ function formatTimeLeft({ timeLeftMs }: { timeLeftMs: number }) { const weeks = Math.floor(days / 7); const daysLeft = days % 7; const extraUnit = daysLeft ? ` ${daysLeft}d` : ''; - return window.i18n('messageWillDisappear', [`${weeks}w${extraUnit}`]); + return window.i18n('messageWillDisappear', { countAndUnit: `${weeks}w${extraUnit}` }); } return '...'; diff --git a/ts/components/conversation/message/message-content/quote/QuoteText.tsx b/ts/components/conversation/message/message-content/quote/QuoteText.tsx index 9caa40004c..9a6dde4c97 100644 --- a/ts/components/conversation/message/message-content/quote/QuoteText.tsx +++ b/ts/components/conversation/message/message-content/quote/QuoteText.tsx @@ -48,7 +48,7 @@ function getTypeLabel({ return window.i18n('image'); } if (MIME.isAudio(contentType) && isVoiceMessage) { - return window.i18n('voiceMessage'); + return window.i18n('messageVoice'); } if (MIME.isAudio(contentType)) { return window.i18n('audio'); @@ -75,7 +75,7 @@ export const QuoteText = ( return ( { {showMemberCount && (
- {window.i18n('members', [`${subscriberCount}`])} + {window.i18n('members', { count: `${subscriberCount}` })}
@@ -259,15 +259,15 @@ export const OverlayRightPanelSettings = () => { const commonNoShow = isKickedFromGroup || left || isBlocked || !isActive; const hasDisappearingMessages = !isPublic && !commonNoShow; const leaveGroupString = isPublic - ? window.i18n('leaveCommunity') + ? window.i18n('communityLeave') : lastMessage?.interactionType === ConversationInteractionType.Leave && lastMessage?.interactionStatus === ConversationInteractionStatus.Error - ? window.i18n('deleteConversation') + ? window.i18n('conversationsDelete') : isKickedFromGroup ? window.i18n('youGotKickedFromGroup') : left - ? window.i18n('youLeftTheGroup') - : window.i18n('leaveGroup'); + ? window.i18n('groupMemberYouLeft') + : window.i18n('groupLeave'); const showUpdateGroupNameButton = isGroup && weAreAdmin && !commonNoShow; // legacy groups non-admin cannot change groupname anymore const showAddRemoveModeratorsButton = weAreAdmin && !commonNoShow && isPublic; @@ -285,7 +285,7 @@ export const OverlayRightPanelSettings = () => { {showUpdateGroupNameButton && ( { void showUpdateGroupNameByConvoId(selectedConvoKey); }} diff --git a/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx b/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx index 8c50402d73..79dc0903ca 100644 --- a/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx +++ b/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx @@ -208,9 +208,9 @@ export const OverlayDisappearingMessages = () => { <> - {window.i18n('settingAppliesToEveryone')} + {window.i18n('disappearingMessagesDescription')}
- {window.i18n('onlyGroupAdminsCanChange')} + {window.i18n('disappearingMessagesOnlyAdmins')}
)} diff --git a/ts/components/dialog/SessionConfirm.tsx b/ts/components/dialog/SessionConfirm.tsx index 550c8439e0..1177765bee 100644 --- a/ts/components/dialog/SessionConfirm.tsx +++ b/ts/components/dialog/SessionConfirm.tsx @@ -217,11 +217,11 @@ export const showLinkVisitWarningDialog = (urlToOpen: string, dispatch: Dispatch dispatch( updateConfirmModal({ - title: window.i18n('linkVisitWarningTitle'), - message: window.i18n('linkVisitWarningMessage', [urlToOpen]), + title: window.i18n('urlOpen'), + message: window.i18n('urlOpenDescription', { url: urlToOpen }), okText: window.i18n('open'), okTheme: SessionButtonColor.Primary, - cancelText: window.i18n('editMenuCopy'), + cancelText: window.i18n('copy'), showExitIcon: true, onClickOk, onClickClose: () => { diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index e6d84d3f9a..3cd2a28f93 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -1681,7 +1681,7 @@ export class ConversationModel extends Backbone.Model { const pubkey = this.id; if (UserUtils.isUsFromCache(pubkey)) { - return window.i18n('you'); + return window.i18n('onionRoutingPathYou'); } const profileName = this.get('displayNameInProfile'); @@ -1741,7 +1741,7 @@ export class ConversationModel extends Backbone.Model { const isFirstMessageOfConvo = (await Data.getMessagesByConversation(this.id, { messageId: null })).messages.length === 1; if (hadNoRequestsPrior && isFirstMessageOfConvo) { - friendRequestText = window.i18n('youHaveANewFriendRequest'); + friendRequestText = window.i18n('messageRequestsNew'); } else { window?.log?.info( 'notification cancelled for as pending requests already exist', @@ -1825,9 +1825,9 @@ export class ConversationModel extends Backbone.Model { conversationId, iconUrl, isExpiringMessage: false, - message: window.i18n('incomingCallFrom', [ - this.getNicknameOrRealUsername() || window.i18n('anonymous'), - ]), + message: window.i18n('callsIncoming', { + name: this.getNicknameOrRealUsername() || window.i18n('anonymous'), + }), messageSentAt: now, title: this.getNicknameOrRealUsernameOrPlaceholder(), }); diff --git a/ts/state/selectors/selectedConversation.ts b/ts/state/selectors/selectedConversation.ts index f6a1182720..d064c582ce 100644 --- a/ts/state/selectors/selectedConversation.ts +++ b/ts/state/selectors/selectedConversation.ts @@ -26,14 +26,13 @@ const getCurrentNotificationSettingText = (state: StateType): string | undefined } const currentNotificationSetting = getSelectedConversation(state)?.currentNotificationSetting; switch (currentNotificationSetting) { - case 'all': - return window.i18n('notificationForConvo_all'); case 'mentions_only': - return window.i18n('notificationForConvo_mentions_only'); - case 'disabled': - return window.i18n('notificationForConvo_disabled'); - default: - return window.i18n('notificationForConvo_all'); + return window.i18n('notificationsMentionsOnly'); + case 'disabled': + return window.i18n('notificationsMute'); + case 'all': + default: + return window.i18n('notificationsAllMessages'); } }; @@ -347,6 +346,7 @@ export function useSelectedShortenedPubkeyOrFallback() { if (isPrivate) { return window.i18n('anonymous'); } + // TODO: String localization - remove return window.i18n('unknown'); } From 43a5d3eb9d9fd8927bea5f36ff9c84e1f6ee0cb1 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Wed, 27 Mar 2024 17:35:00 +1100 Subject: [PATCH 08/93] feat: strict typing for i18n function and component --- ts/types/Localizer.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 ts/types/Localizer.ts diff --git a/ts/types/Localizer.ts b/ts/types/Localizer.ts new file mode 100644 index 0000000000..4e2242520e --- /dev/null +++ b/ts/types/Localizer.ts @@ -0,0 +1,31 @@ +import type { Dictionary } from '../localization/locales'; + +/** A localization dictionary key */ +type Token = keyof Dictionary; + +/** A dynamic argument that can be used in a localized string */ +type DynamicArg = string | number; + +/** A record of dynamic arguments for a specific key in the localization dictionary */ +type ArgsRecord = Record, DynamicArg>; + +/** The dynamic arguments in a localized string */ +type DynamicArgs = + // eslint-disable-next-line @typescript-eslint/no-unused-vars + LocalizedString extends `${infer _Pre}{${infer Var}}${infer Rest}` + ? Var | DynamicArgs + : never; + +/** The arguments for retrieving a localized message */ +export type GetMessageArgs = + DynamicArgs extends never ? [T] : [T, ArgsRecord]; + +/** The props for the localization component */ +export type I18nProps = + DynamicArgs extends never ? { token: T } : { token: T; args: ArgsRecord }; + +/** The dictionary of localized strings */ +export type LocalizerDictionary = Dictionary; + +/** A localization dictionary key */ +export type LocalizerToken = keyof LocalizerDictionary; From 842a8792b5aa0595690a42e0acaee649a8f9e119 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Wed, 27 Mar 2024 17:36:07 +1100 Subject: [PATCH 09/93] feat: add i18n component for rendering html from a localized string --- ts/components/basic/I18n.tsx | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 ts/components/basic/I18n.tsx diff --git a/ts/components/basic/I18n.tsx b/ts/components/basic/I18n.tsx new file mode 100644 index 0000000000..1b82ca51b6 --- /dev/null +++ b/ts/components/basic/I18n.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import type { + GetMessageArgs, + I18nProps, + LocalizerDictionary, + LocalizerToken, +} from '../../types/Localizer'; + +import { SessionHtmlRenderer } from './SessionHTMLRenderer'; + +/** An array of supported html tags to render if found in a string */ +const supportedFormattingTags = ['b', 'i', 'u', 's']; + +/** A regex to match supported formatting tags */ +const formattingTagRegex = new RegExp( + `<(?:${supportedFormattingTags.join('|')})>.*?`, + 'g' +); + +/** + * Retrieve a localized message string, substituting dynamic parts where necessary and formatting it as HTML if necessary. + * + * @param token - The token identifying the message to retrieve and an optional record of substitution variables and their replacement values. + * @param args - An optional record of substitution variables and their replacement values. This is required if the string has dynamic parts. + * + * @returns The localized message string with substitutions and formatting applied. + * + * @example + * ```tsx + * + * + * ``` + */ +export const I18n = (props: I18nProps) => { + const i18nArgs = 'args' in props ? props.args : undefined; + + const i18nString = window.i18n( + ...([props.token, i18nArgs] as GetMessageArgs) + ); + + /** If the string contains a relevant formatting tag, render it as HTML */ + if (i18nString.match(formattingTagRegex)) { + return ; + } + + return <>{i18nString}; +}; From 90283077df3ee7dbddf40fb8da9d76801cf04060 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Wed, 27 Mar 2024 17:46:36 +1100 Subject: [PATCH 10/93] feat: rework i18n getMessages to use named dynamic vairiables and strict types --- ts/util/i18n.ts | 80 +++++++++++++++++++++++++++++++++---------------- ts/window.d.ts | 24 ++++++++++++--- 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/ts/util/i18n.ts b/ts/util/i18n.ts index 732fb25800..11c146c5f9 100644 --- a/ts/util/i18n.ts +++ b/ts/util/i18n.ts @@ -1,42 +1,70 @@ // this file is a weird one as it is used by both sides of electron at the same time -import { LocaleMessagesType } from '../node/locale'; -import { LocalizerKeys } from '../types/LocalizerKeys'; +import { isUndefined } from 'lodash'; +import { GetMessageArgs, LocalizerDictionary, LocalizerToken } from '../types/Localizer'; + +/** + * Logs an i18n message to the console. + * @param message - The message to log. + * + * TODO - Replace this logging method when the new logger is created + */ +function i18nLog(message: string) { + // eslint:disable: no-console + // eslint-disable-next-line no-console + (window.log.error || console.log)(message); +} -export const setupi18n = (locale: string, messages: LocaleMessagesType) => { +/** + * Sets up the i18n function with the provided locale and messages. + * + * @param locale - The locale to use for translations. + * @param dictionary - A dictionary of localized messages. + * + * @returns A function that retrieves a localized message string, substituting variables where necessary. + */ +export const setupi18n = (locale: string, dictionary: LocalizerDictionary) => { if (!locale) { throw new Error('i18n: locale parameter is required'); } - if (!messages) { + if (!dictionary) { throw new Error('i18n: messages parameter is required'); } - function getMessage(key: LocalizerKeys, substitutions: Array) { - const message = messages[key]; - if (!message) { - // eslint:disable: no-console - // eslint-disable-next-line no-console - (window.log.error || console.log)( - `i18n: Attempted to get translation for nonexistent key '${key}'` - ); - return ''; + /** + * Retrieves a localized message string, substituting variables where necessary. + * + * @param token - The token identifying the message to retrieve. + * @param args - An optional record of substitution variables and their replacement values. This is required if the string has dynamic variables. + * + * @returns The localized message string with substitutions applied. + * + * @example + * // The string greeting is 'Hello, {name}!' in the current locale + * window.i18n('greeting', { name: 'Alice' }); + * // => 'Hello, Alice!' + */ + function getMessage( + ...[token, args]: GetMessageArgs + ): R { + const localizedString = dictionary[token]; + + if (!localizedString) { + i18nLog(`i18n: Attempted to get translation for nonexistent key '${token}'`); + return '' as R; } - if (Array.isArray(substitutions)) { - const replacedNameDollarSign = message.replaceAll('$', 'ᅲ'); - - const substituted = substitutions.reduce( - (result, substitution) => result.replace(/ᅲ.+?ᅲ/, substitution), - replacedNameDollarSign - ); - - return substituted.replaceAll('ᅲ', '$'); - } - if (substitutions) { - return message.replace(/\$.+?\$/, substitutions); + /** If a localized string does not have any arguments to substitute it is retured with no changes */ + if (!args) { + return localizedString as R; } - return message; + /** Find and replace the dynamic variables in a localized string and substitute the variables with the provided values */ + return localizedString.replace(/\{(\w+)\}/g, (match, arg: keyof typeof args) => { + const substitution = args[arg]; + /** If a substitution is undefined we return the variable match */ + return isUndefined(substitution) ? match : substitution.toString(); + }) as R; } getMessage.getLocale = () => locale; diff --git a/ts/window.d.ts b/ts/window.d.ts index b90a6b03b8..a8874679a1 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -1,11 +1,11 @@ // eslint-disable-next-line import/no-unresolved import {} from 'styled-components/cssprop'; -import { LocalizerType } from './types/Util'; - import { ConversationCollection } from './models/conversation'; import { PrimaryColorStateType, ThemeStateType } from './themes/constants/colors'; +import type { GetMessageArgs, LocalizerDictionary, LocalizerToken } from './types/Localizer'; + export interface LibTextsecure { messaging: boolean; } @@ -24,8 +24,24 @@ declare global { clipboard: any; getSettingValue: (id: string, comparisonValue?: any) => any; setSettingValue: (id: string, value: any) => Promise; - - i18n: LocalizerType; + /** + * Retrieves a localized message string, substituting variables where necessary. + * + * @param token - The token identifying the message to retrieve. + * @param args - An optional record of substitution variables and their replacement values. This is required if the string has dynamic variables. + * + * @returns The localized message string with substitutions applied. + * + * @link [i18n](./util/i18n.ts) + * + * @example + * // The string greeting is 'Hello, {name}!' in the current locale + * window.i18n('greeting', { name: 'Alice' }); + * // => 'Hello, Alice!' + */ + i18n: ( + ...[token, args]: GetMessageArgs + ) => R; log: any; sessionFeatureFlags: { useOnionRequests: boolean; From 665d8d6f8c6a8fefd3ba32f52d49fcac6825a3ac Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 09:26:45 +1100 Subject: [PATCH 11/93] feat: add as prop to I18n component for rendering a specific tag --- ts/components/basic/I18n.tsx | 8 ++++++-- ts/types/Localizer.ts | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ts/components/basic/I18n.tsx b/ts/components/basic/I18n.tsx index 1b82ca51b6..5b29dcb710 100644 --- a/ts/components/basic/I18n.tsx +++ b/ts/components/basic/I18n.tsx @@ -22,12 +22,14 @@ const formattingTagRegex = new RegExp( * * @param token - The token identifying the message to retrieve and an optional record of substitution variables and their replacement values. * @param args - An optional record of substitution variables and their replacement values. This is required if the string has dynamic parts. + * @param as - An optional HTML tag to render the component as. Defaults to a fragment, unless the string contains html tags. In that case, it will render as HTML in a div tag. * * @returns The localized message string with substitutions and formatting applied. * * @example * ```tsx * + * * * ``` */ @@ -40,8 +42,10 @@ export const I18n = (props: I18nProps) => { /** If the string contains a relevant formatting tag, render it as HTML */ if (i18nString.match(formattingTagRegex)) { - return ; + return ; } - return <>{i18nString}; + const Comp = props.as ?? React.Fragment; + + return {i18nString}; }; diff --git a/ts/types/Localizer.ts b/ts/types/Localizer.ts index 4e2242520e..383fe5c9f3 100644 --- a/ts/types/Localizer.ts +++ b/ts/types/Localizer.ts @@ -1,3 +1,4 @@ +import type { ElementType } from 'react'; import type { Dictionary } from '../localization/locales'; /** A localization dictionary key */ @@ -20,9 +21,14 @@ type DynamicArgs = export type GetMessageArgs = DynamicArgs extends never ? [T] : [T, ArgsRecord]; +/** Basic props for all calls of the I18n component */ +type I18nBaseProps = { token: T; as?: ElementType }; + /** The props for the localization component */ export type I18nProps = - DynamicArgs extends never ? { token: T } : { token: T; args: ArgsRecord }; + DynamicArgs extends never + ? I18nBaseProps + : I18nBaseProps & { args: ArgsRecord }; /** The dictionary of localized strings */ export type LocalizerDictionary = Dictionary; From 7ba74f8e7d4fcebba2bd517bdd755676a99c1219 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 09:30:01 +1100 Subject: [PATCH 12/93] fix: add React.ElementType type to SessionHTMLRenderer and rename tag prop to as --- ts/components/basic/I18n.tsx | 2 +- ts/components/basic/SessionHTMLRenderer.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ts/components/basic/I18n.tsx b/ts/components/basic/I18n.tsx index 5b29dcb710..c248a41ff7 100644 --- a/ts/components/basic/I18n.tsx +++ b/ts/components/basic/I18n.tsx @@ -42,7 +42,7 @@ export const I18n = (props: I18nProps) => { /** If the string contains a relevant formatting tag, render it as HTML */ if (i18nString.match(formattingTagRegex)) { - return ; + return ; } const Comp = props.as ?? React.Fragment; diff --git a/ts/components/basic/SessionHTMLRenderer.tsx b/ts/components/basic/SessionHTMLRenderer.tsx index 7e5280a0b5..83c0c46ea6 100644 --- a/ts/components/basic/SessionHTMLRenderer.tsx +++ b/ts/components/basic/SessionHTMLRenderer.tsx @@ -1,20 +1,20 @@ -import React from 'react'; import DOMPurify from 'dompurify'; +import React from 'react'; type ReceivedProps = { html: string; - tag?: string; + as?: React.ElementType; key?: any; className?: string; }; -export const SessionHtmlRenderer = ({ tag = 'div', key, html, className }: ReceivedProps) => { +export const SessionHtmlRenderer = ({ as = 'div', key, html, className }: ReceivedProps) => { const clean = DOMPurify.sanitize(html, { USE_PROFILES: { html: true }, FORBID_ATTR: ['script'], }); - return React.createElement(tag, { + return React.createElement(as, { key, className, From 64b202a49f3c2d62da9c7610240a889ab2b21d80 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 13:38:10 +1100 Subject: [PATCH 13/93] feat: remove strings no longer supported by the localization project --- ts/components/CaptionEditor.tsx | 9 +-------- ts/components/conversation/Image.tsx | 1 - ts/components/conversation/StagedAttachmentList.tsx | 2 +- ts/components/conversation/StagedLinkPreview.tsx | 2 +- ts/components/conversation/TypingAnimation.tsx | 2 +- .../conversation/media-gallery/MediaGridItem.tsx | 3 --- .../message/message-content/MessageLinkPreview.tsx | 2 +- ts/components/lightbox/Lightbox.tsx | 1 - 8 files changed, 5 insertions(+), 17 deletions(-) diff --git a/ts/components/CaptionEditor.tsx b/ts/components/CaptionEditor.tsx index 20e51b2cef..1dd9494ce7 100644 --- a/ts/components/CaptionEditor.tsx +++ b/ts/components/CaptionEditor.tsx @@ -17,14 +17,7 @@ const CaptionEditorObject = (props: Props) => { const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); if (isImageTypeSupported) { - return ( - {window.i18n('imageAttachmentAlt')} - ); + return ; } const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType); diff --git a/ts/components/conversation/Image.tsx b/ts/components/conversation/Image.tsx index dc051e2197..d29c627361 100644 --- a/ts/components/conversation/Image.tsx +++ b/ts/components/conversation/Image.tsx @@ -145,7 +145,6 @@ export const Image = (props: Props) => { {window.i18n('imageCaptionIconAlt')} ) : null} diff --git a/ts/components/conversation/StagedAttachmentList.tsx b/ts/components/conversation/StagedAttachmentList.tsx index cf5d3218a1..2211e814d2 100644 --- a/ts/components/conversation/StagedAttachmentList.tsx +++ b/ts/components/conversation/StagedAttachmentList.tsx @@ -85,7 +85,7 @@ export const StagedAttachmentList = (props: Props) => { return ( {window.i18n('stagedImageAttachment', { {isLoaded && image && isContentTypeImage ? ( {window.i18n('stagedPreviewThumbnail', { return ( - + diff --git a/ts/components/conversation/media-gallery/MediaGridItem.tsx b/ts/components/conversation/media-gallery/MediaGridItem.tsx index 1b4822afae..6557bf1968 100644 --- a/ts/components/conversation/media-gallery/MediaGridItem.tsx +++ b/ts/components/conversation/media-gallery/MediaGridItem.tsx @@ -15,7 +15,6 @@ type Props = { const MediaGridItemContent = (props: Props) => { const { mediaItem } = props; - const i18n = window.i18n; const { attachment, contentType } = mediaItem; const urlToDecrypt = mediaItem.thumbnailObjectUrl || ''; @@ -50,7 +49,6 @@ const MediaGridItemContent = (props: Props) => { return ( {i18n('lightboxImageAlt')} { return (
{i18n('lightboxImageAlt')} {
{window.i18n('previewThumbnail', From 7fbb5ef161780fe42c5fcab21666037d5edf9fef Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 14:17:22 +1100 Subject: [PATCH 14/93] feat: replace and add changed and new localized strings --- ts/components/DebugLogView.tsx | 10 +- ts/components/SessionPasswordPrompt.tsx | 8 +- ts/components/SessionSearchInput.tsx | 4 +- ts/components/basic/YourSessionIDPill.tsx | 2 +- .../calling/InConversationCallContainer.tsx | 4 +- ts/components/calling/IncomingCallDialog.tsx | 6 +- ts/components/conversation/AddMentions.tsx | 2 +- .../conversation/SessionConversation.tsx | 14 +-- .../conversation/SessionLastSeenIndicator.tsx | 2 +- .../conversation/TimerNotification.tsx | 29 ++--- .../composition/CompositionBox.tsx | 6 +- .../header/ConversationHeaderTitle.tsx | 7 +- .../media-gallery/MediaGallery.tsx | 6 +- .../message-content/ClickToTrustSender.tsx | 12 +-- .../message-content/MessageContextMenu.tsx | 14 ++- .../message-content/MessageReactions.tsx | 2 +- .../message/message-content/MessageStatus.tsx | 5 +- .../message/message-content/MessageText.tsx | 10 +- .../message/message-content/Quote.tsx | 6 +- .../DataExtractionNotification.tsx | 10 +- .../message/message-item/GroupInvitation.tsx | 2 +- .../message-item/GroupUpdateMessage.tsx | 19 ++-- .../message-item/InteractionNotification.tsx | 4 +- .../message-item/MessageRequestResponse.tsx | 6 +- .../DisappearingModes.tsx | 12 +-- .../OverlayDisappearingMessages.tsx | 6 +- .../disappearing-messages/TimeOptions.tsx | 2 +- .../message-info/OverlayMessageInfo.tsx | 2 +- .../components/AttachmentInfo.tsx | 18 ++-- .../message-info/components/MessageInfo.tsx | 2 +- ts/components/dialog/BanOrUnbanUserDialog.tsx | 8 +- ts/components/dialog/DeleteAccountModal.tsx | 39 ++++--- ts/components/dialog/EditProfileDialog.tsx | 6 +- .../dialog/EditProfilePictureModal.tsx | 2 +- ts/components/dialog/InviteContactsDialog.tsx | 7 +- ts/components/dialog/ModeratorsAddDialog.tsx | 2 +- .../dialog/OnionStatusPathDialog.tsx | 12 +-- ts/components/dialog/ReactClearAllModal.tsx | 2 +- ts/components/dialog/ReactListModal.tsx | 18 ++-- .../dialog/SessionNicknameDialog.tsx | 6 +- .../dialog/SessionPasswordDialog.tsx | 44 ++++---- ts/components/dialog/SessionSeedModal.tsx | 11 +- .../dialog/UpdateGroupMembersDialog.tsx | 8 +- .../dialog/UpdateGroupNameDialog.tsx | 11 +- ts/components/dialog/UserDetailsDialog.tsx | 4 +- ts/components/leftpane/ContactListItem.tsx | 2 +- .../leftpane/LeftPaneSectionHeader.tsx | 9 +- .../leftpane/LeftPaneSettingSection.tsx | 16 +-- .../leftpane/MessageRequestsBanner.tsx | 2 +- .../InteractionItem.tsx | 2 +- .../leftpane/overlay/OverlayClosedGroup.tsx | 16 +-- .../leftpane/overlay/OverlayCommunity.tsx | 6 +- .../leftpane/overlay/OverlayMessage.tsx | 12 +-- .../overlay/OverlayMessageRequest.tsx | 6 +- .../overlay/SessionJoinableDefaultRooms.tsx | 2 +- .../choose-action/ContactsListWithBreaks.tsx | 2 +- .../choose-action/OverlayChooseAction.tsx | 6 +- .../menu/ConversationListItemContextMenu.tsx | 4 +- ts/components/menu/Menu.tsx | 45 ++++---- .../menu/MessageRequestBannerContextMenu.tsx | 2 +- .../registration/RegistrationStages.tsx | 2 +- .../registration/RegistrationUserDetails.tsx | 8 +- ts/components/registration/SignInTab.tsx | 10 +- ts/components/registration/SignUpTab.tsx | 6 +- ts/components/search/MessageSearchResults.tsx | 8 +- ts/components/search/SearchResults.tsx | 4 +- ts/components/settings/BlockedList.tsx | 13 ++- .../SessionNotificationGroupSettings.tsx | 18 ++-- ts/components/settings/SessionSettings.tsx | 4 +- .../settings/SessionSettingsHeader.tsx | 12 +-- .../settings/SettingsThemeSwitcher.tsx | 6 +- .../settings/ZoomingSessionSlider.tsx | 2 +- .../settings/section/CategoryAppearance.tsx | 8 +- .../section/CategoryConversations.tsx | 22 ++-- .../settings/section/CategoryHelp.tsx | 14 +-- .../settings/section/CategoryPermissions.tsx | 20 ++-- .../settings/section/CategoryPrivacy.tsx | 33 +++--- ts/hooks/useParamSelector.ts | 12 +-- ts/interactions/conversationInteractions.ts | 48 +++++---- .../conversations/unsendingInteractions.ts | 27 ++--- ts/interactions/messageInteractions.ts | 10 +- ts/mains/main_node.ts | 21 ++-- ts/models/message.ts | 87 ++++++++------- ts/node/menu.ts | 64 +++++------ ts/node/spell_check.ts | 12 +-- ts/node/sql.ts | 8 +- ts/node/tray_icon.ts | 8 +- .../opengroupV2/JoinOpenGroupV2.ts | 15 ++- .../opengroupV2/OpenGroupManagerV2.ts | 2 +- ts/session/conversations/createClosedGroup.ts | 11 +- .../disappearing_messages/timerOptions.ts | 34 +++++- ts/session/types/PubKey.ts | 6 +- ts/session/utils/Toast.tsx | 101 +++++++----------- ts/state/selectors/messages.ts | 2 +- ts/state/selectors/search.ts | 4 +- ts/state/selectors/selectedConversation.ts | 8 +- .../unit/selectors/conversations_test.ts | 11 +- ts/themes/constants/colors.tsx | 8 +- ts/types/Attachment.ts | 5 +- ts/updater/common.ts | 20 ++-- ts/util/notifications.ts | 12 ++- ts/util/passwordUtils.ts | 8 +- 102 files changed, 658 insertions(+), 620 deletions(-) diff --git a/ts/components/DebugLogView.tsx b/ts/components/DebugLogView.tsx index 3caa50b485..153a954478 100644 --- a/ts/components/DebugLogView.tsx +++ b/ts/components/DebugLogView.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { switchThemeTo } from '../themes/switchTheme'; import { SessionTheme } from '../themes/SessionTheme'; +import { switchThemeTo } from '../themes/switchTheme'; import { fetch } from '../util/logging'; import { SessionButton, SessionButtonType } from './basic/SessionButton'; import { SessionIconButton } from './icon'; @@ -55,7 +55,7 @@ const DebugLogButtons = (props: { content: string }) => { return (
{ if (props.content.length <= 20) { @@ -70,7 +70,7 @@ const DebugLogButtons = (props: { content: string }) => { }; const DebugLogViewAndSave = () => { - const [content, setContent] = useState(window.i18n('loading')); + const [content, setContent] = useState(window.i18n('loading')); useEffect(() => { const operatingSystemInfo = `Operating System: ${(window as any).getOSRelease()}`; @@ -116,8 +116,8 @@ export const DebugLogView = () => { window.closeDebugLog(); }} /> -

{window.i18n('debugLog')}

-

{window.i18n('debugLogExplanation')}

+

{window.i18n('helpReportABugExportLogs')}

+

{window.i18n('helpReportABugExportLogsSaveToDesktopDescription')}

diff --git a/ts/components/SessionPasswordPrompt.tsx b/ts/components/SessionPasswordPrompt.tsx index 929db35c8a..5f501425de 100644 --- a/ts/components/SessionPasswordPrompt.tsx +++ b/ts/components/SessionPasswordPrompt.tsx @@ -70,14 +70,14 @@ class SessionPasswordPromptInner extends React.PureComponent { const isLoading = this.state.loading; const spinner = isLoading ? : null; const featureElement = this.state.clearDataView ? ( -

{window.i18n('deleteAccountFromLogin')}

+

{window.i18n('clearDeviceDescription')}

) : (
{ this.inputRef = input; @@ -88,9 +88,7 @@ class SessionPasswordPromptInner extends React.PureComponent { return ( {spinner || featureElement} diff --git a/ts/components/SessionSearchInput.tsx b/ts/components/SessionSearchInput.tsx index 389644d043..d91b627b33 100644 --- a/ts/components/SessionSearchInput.tsx +++ b/ts/components/SessionSearchInput.tsx @@ -84,9 +84,7 @@ export const SessionSearchInput = () => { return null; } - const placeholder = isGroupCreationSearch - ? window.i18n('searchForContactsOnly') - : window.i18n('searchFor...'); + const placeholder = isGroupCreationSearch ? window.i18n('searchContacts') : window.i18n('search'); return ( diff --git a/ts/components/basic/YourSessionIDPill.tsx b/ts/components/basic/YourSessionIDPill.tsx index 54dd01adcb..1aa81790a6 100644 --- a/ts/components/basic/YourSessionIDPill.tsx +++ b/ts/components/basic/YourSessionIDPill.tsx @@ -28,7 +28,7 @@ export const YourSessionIDPill = () => { return ( - {window.i18n('yourSessionID')} + {window.i18n('accountIdYours')} ); diff --git a/ts/components/calling/InConversationCallContainer.tsx b/ts/components/calling/InConversationCallContainer.tsx index dfedb85ae6..d07007cb0d 100644 --- a/ts/components/calling/InConversationCallContainer.tsx +++ b/ts/components/calling/InConversationCallContainer.tsx @@ -75,7 +75,7 @@ const StyledCenteredLabel = styled.div` const RingingLabel = () => { const ongoingCallWithFocusedIsRinging = useSelector(getCallWithFocusedConvoIsOffering); - const modulatedStr = useModuloWithTripleDots(window.i18n('ringing'), 3, 1000); + const modulatedStr = useModuloWithTripleDots(window.i18n('callsRinging'), 3, 1000); if (!ongoingCallWithFocusedIsRinging) { return null; } @@ -85,7 +85,7 @@ const RingingLabel = () => { const ConnectingLabel = () => { const ongoingCallWithFocusedIsConnecting = useSelector(getCallWithFocusedConvosIsConnecting); - const modulatedStr = useModuloWithTripleDots(window.i18n('establishingConnection'), 3, 1000); + const modulatedStr = useModuloWithTripleDots(window.i18n('callsConnecting'), 3, 1000); if (!ongoingCallWithFocusedIsConnecting) { return null; diff --git a/ts/components/calling/IncomingCallDialog.tsx b/ts/components/calling/IncomingCallDialog.tsx index ccaba5c465..3f9824b0e2 100644 --- a/ts/components/calling/IncomingCallDialog.tsx +++ b/ts/components/calling/IncomingCallDialog.tsx @@ -75,7 +75,11 @@ export const IncomingCallDialog = () => { if (hasIncomingCall) { return ( - + diff --git a/ts/components/conversation/AddMentions.tsx b/ts/components/conversation/AddMentions.tsx index 0246683267..f7716a4a52 100644 --- a/ts/components/conversation/AddMentions.tsx +++ b/ts/components/conversation/AddMentions.tsx @@ -30,7 +30,7 @@ const Mention = (props: MentionProps) => { // this call takes care of finding if we have a blindedId of ourself on any sogs we have joined. if (isUsAnySogsFromCache(blindedOrNotPubkey)) { - return @{window.i18n('you')}; + return @{window.i18n('onionRoutingPathYou')}; } return ( diff --git a/ts/components/conversation/SessionConversation.tsx b/ts/components/conversation/SessionConversation.tsx index 708e5e481b..4a45025182 100644 --- a/ts/components/conversation/SessionConversation.tsx +++ b/ts/components/conversation/SessionConversation.tsx @@ -127,10 +127,8 @@ export class SessionConversation extends React.Component { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public componentDidUpdate(prevProps: Props, _prevState: State) { - const { - selectedConversationKey: newConversationKey, - selectedConversation: newConversation, - } = this.props; + const { selectedConversationKey: newConversationKey, selectedConversation: newConversation } = + this.props; const { selectedConversationKey: oldConversationKey } = prevProps; // if the convo is valid, and it changed, register for drag events @@ -211,8 +209,8 @@ export class SessionConversation extends React.Component { if (msg.body.replace(/\s/g, '').includes(recoveryPhrase.replace(/\s/g, ''))) { window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('sendRecoveryPhraseTitle'), - message: window.i18n('sendRecoveryPhraseMessage'), + title: window.i18n('warning'), + message: window.i18n('recoveryPasswordWarningSendDescription'), okTheme: SessionButtonColor.Danger, onClickOk: () => { void sendAndScroll(); @@ -255,7 +253,9 @@ export class SessionConversation extends React.Component { const bannerText = selectedConversation.hasOutdatedClient && selectedConversation.hasOutdatedClient !== ourDisplayNameInProfile - ? window.i18n('disappearingMessagesModeOutdated', [selectedConversation.hasOutdatedClient]) + ? window.i18n('disappearingMessagesLegacy', { + name: selectedConversation.hasOutdatedClient, + }) : window.i18n('someOfYourDeviceUseOutdatedVersion'); return ( diff --git a/ts/components/conversation/SessionLastSeenIndicator.tsx b/ts/components/conversation/SessionLastSeenIndicator.tsx index 9b65f07cd0..a4062da330 100644 --- a/ts/components/conversation/SessionLastSeenIndicator.tsx +++ b/ts/components/conversation/SessionLastSeenIndicator.tsx @@ -76,7 +76,7 @@ export const SessionLastSeenIndicator = (props: { return ( - {window.i18n('unreadMessages')} + {window.i18n('messageUnread')} diff --git a/ts/components/conversation/TimerNotification.tsx b/ts/components/conversation/TimerNotification.tsx index f0a3b9f8c9..cd33bb6696 100644 --- a/ts/components/conversation/TimerNotification.tsx +++ b/ts/components/conversation/TimerNotification.tsx @@ -46,18 +46,18 @@ function useFollowSettingsButtonClick( const doIt = () => { const mode = props.expirationMode === 'deleteAfterRead' - ? window.i18n('timerModeRead') - : window.i18n('timerModeSent'); + ? window.i18n('disappearingMessagesTypeRead') + : window.i18n('disappearingMessagesTypeSent'); const message = props.disabled - ? window.i18n('followSettingDisabled') + ? window.i18n('disappearingMessagesFollowSettingOff') : window.i18n('followSettingTimeAndType', { time: props.timespanText, type: mode, }); - const okText = props.disabled ? window.i18n('confirm') : window.i18n('set'); + const okText = props.disabled ? window.i18n('yes') : window.i18n('set'); dispatch( updateConfirmModal({ - title: window.i18n('followSetting'), + title: window.i18n('disappearingMessagesFollowSetting'), message, okText, okTheme: SessionButtonColor.Danger, @@ -131,7 +131,7 @@ const FollowSettingsButton = (props: PropsForExpirationTimer) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises onClick={() => click.doIt()} > - {window.i18n('followSetting')} + {window.i18n('disappearingMessagesFollowSetting')} ); }; @@ -150,22 +150,20 @@ function useTextToRender(props: PropsForExpirationTimer) { const mode = isLegacyDisappearingModeEnabled(expirationMode) ? null : expirationMode === 'deleteAfterRead' - ? window.i18n('timerModeRead') - : window.i18n('timerModeSent'); + ? window.i18n('disappearingMessagesTypeRead') + : window.i18n('disappearingMessagesTypeSent'); switch (type) { case 'fromOther': if (disabled) { - return ownSideOnly - ? window.i18n('theyDisabledTheirDisappearingMessages', { name: contact }) - : window.i18n('disappearingMessagesTurnedOff', { name: contact }); + return window.i18n('disappearingMessagesTurnedOff', { name: contact }); } if (mode) { return ownSideOnly - ? window.i18n('theySetTheirDisappearingMessages', { + ? window.i18n('disappearingMessagesChanged', { name: contact, time: timespanText, - type: mode, + disappearingmessagestype: mode, }) : window.i18n('theyChangedTheTimer', { name: contact, @@ -186,7 +184,10 @@ function useTextToRender(props: PropsForExpirationTimer) { } if (mode) { return ownSideOnly - ? window.i18n('youSetYourDisappearingMessages', { time: timespanText, type: mode }) + ? window.i18n('disappearingMessagesSetYou', { + time: timespanText, + disappearingmessagestype: mode, + }) : window.i18n('youChangedTheTimer', { time: timespanText, mode }); } return window.i18n('youChangedTheTimerLegacy', { time: timespanText }); diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index 466a9aeb00..498e1d7df7 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -484,12 +484,12 @@ class CompositionBoxInner extends React.Component { return i18n('youGotKickedFromGroup'); } if (left) { - return i18n('youLeftTheGroup'); + return i18n('groupMemberYouLeft'); } if (isBlocked) { - return i18n('unblockToSend'); + return i18n('blockBlockedDescription'); } - return i18n('sendMessage'); + return i18n('message'); }; const { isKickedFromGroup, left, isBlocked } = this.props.selectedConversation; diff --git a/ts/components/conversation/header/ConversationHeaderTitle.tsx b/ts/components/conversation/header/ConversationHeaderTitle.tsx index 9e42b55766..fa660fa169 100644 --- a/ts/components/conversation/header/ConversationHeaderTitle.tsx +++ b/ts/components/conversation/header/ConversationHeaderTitle.tsx @@ -51,9 +51,8 @@ export const ConversationHeaderTitle = () => { abbreviate: true, }); - const [visibleSubtitle, setVisibleSubtitle] = useState( - 'disappearingMessages' - ); + const [visibleSubtitle, setVisibleSubtitle] = + useState('disappearingMessages'); const [subtitleStrings, setSubtitleStrings] = useState({}); const [subtitleArray, setSubtitleArray] = useState>([]); @@ -77,7 +76,7 @@ export const ConversationHeaderTitle = () => { if (isGroup && memberCount > 0 && !isKickedFromGroup) { const count = String(memberCount); - return i18n(isPublic ? 'activeMembers' : 'members', { count }); + return i18n(isPublic ? 'membersActive' : 'members', { count }); } return null; diff --git a/ts/components/conversation/media-gallery/MediaGallery.tsx b/ts/components/conversation/media-gallery/MediaGallery.tsx index 9cd558233e..8e5b403bf5 100644 --- a/ts/components/conversation/media-gallery/MediaGallery.tsx +++ b/ts/components/conversation/media-gallery/MediaGallery.tsx @@ -43,7 +43,9 @@ const Sections = (props: Props & { selectedTab: TabType }) => { if (!mediaItems || mediaItems.length === 0) { const label = - type === 'media' ? window.i18n('mediaEmptyState') : window.i18n('documentsEmptyState'); + type === 'media' + ? window.i18n('attachmentsMediaEmpty') + : window.i18n('attachmentsFilesEmpty'); return ; } @@ -74,7 +76,7 @@ export const MediaGallery = (props: Props) => {
diff --git a/ts/components/conversation/message/message-content/ClickToTrustSender.tsx b/ts/components/conversation/message/message-content/ClickToTrustSender.tsx index d54733a1ac..a5b7574361 100644 --- a/ts/components/conversation/message/message-content/ClickToTrustSender.tsx +++ b/ts/components/conversation/message/message-content/ClickToTrustSender.tsx @@ -38,12 +38,12 @@ export const ClickToTrustSender = (props: { messageId: string }) => { const convo = getConversationController().get(sender); window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('trustThisContactDialogTitle', [ - convo.getContactProfileNameOrShortenedPubKey(), - ]), - message: window.i18n('trustThisContactDialogDescription', [ - convo.getContactProfileNameOrShortenedPubKey(), - ]), + title: window.i18n('trustThisContactDialogTitle', { + name: convo.getContactProfileNameOrShortenedPubKey(), + }), + message: window.i18n('attachmentsAutoDownloadModalDescription', { + conversationname: convo.getContactProfileNameOrShortenedPubKey(), + }), closeTheme: SessionButtonColor.Danger, onClickOk: async () => { convo.set({ isTrustedForAttachmentDownload: true }); diff --git a/ts/components/conversation/message/message-content/MessageContextMenu.tsx b/ts/components/conversation/message/message-content/MessageContextMenu.tsx index 3b7d8927bd..1c939b22c0 100644 --- a/ts/components/conversation/message/message-content/MessageContextMenu.tsx +++ b/ts/components/conversation/message/message-content/MessageContextMenu.tsx @@ -148,7 +148,7 @@ const AdminActionItems = ({ messageId }: MessageId) => { return showAdminActions ? ( <> {window.i18n('banUser')} - {window.i18n('unbanUser')} + {window.i18n('banUnbanUser')} {isSenderAdmin ? ( {window.i18n('removeFromModerators')} ) : ( @@ -246,7 +246,7 @@ export const MessageContextMenu = (props: Props) => { [showEmojiPanel] ); - const selectMessageText = window.i18n('selectMessage'); + const selectMessageText = window.i18n('messageSelect'); const onReply = useCallback(() => { if (isSelectedBlocked) { @@ -375,18 +375,16 @@ export const MessageContextMenu = (props: Props) => { /> )} {attachments?.length ? ( - {window.i18n('downloadAttachment')} + {window.i18n('attachmentsDownload')} ) : null} - {window.i18n('copyMessage')} - {(isSent || !isOutgoing) && ( - {window.i18n('replyToMessage')} - )} + {window.i18n('messageCopy')} + {(isSent || !isOutgoing) && {window.i18n('reply')}} { void showMessageInfoOverlay({ messageId, dispatch }); }} > - {window.i18n('moreInformation')} + {window.i18n('info')} {isDeletable ? {selectMessageText} : null} diff --git a/ts/components/conversation/message/message-content/MessageReactions.tsx b/ts/components/conversation/message/message-content/MessageReactions.tsx index 0ce6a4fa1b..6d2093c715 100644 --- a/ts/components/conversation/message/message-content/MessageReactions.tsx +++ b/ts/components/conversation/message/message-content/MessageReactions.tsx @@ -126,7 +126,7 @@ const ExpandedReactions = (props: ExpandReactionsProps): ReactElement => { - {window.i18n('expandedReactionsText')} + {window.i18n('showLess')} ); diff --git a/ts/components/conversation/message/message-content/MessageStatus.tsx b/ts/components/conversation/message/message-content/MessageStatus.tsx index d28a41ef70..87d2de1824 100644 --- a/ts/components/conversation/message/message-content/MessageStatus.tsx +++ b/ts/components/conversation/message/message-content/MessageStatus.tsx @@ -242,7 +242,10 @@ const MessageStatusError = ({ dataTestId }: Omit) => { isIncoming={false} isGroup={isGroup} > - + ); diff --git a/ts/components/conversation/message/message-content/MessageText.tsx b/ts/components/conversation/message/message-content/MessageText.tsx index 15ef56bd1c..9a239f38dd 100644 --- a/ts/components/conversation/message/message-content/MessageText.tsx +++ b/ts/components/conversation/message/message-content/MessageText.tsx @@ -3,13 +3,13 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { isOpenOrClosedGroup } from '../../../../models/conversationAttributes'; import { MessageRenderingProps } from '../../../../models/messageType'; +import { StateType } from '../../../../state/reducer'; import { getMessageTextProps, isMessageSelectionMode, } from '../../../../state/selectors/conversations'; import { SessionIcon } from '../../../icon'; import { MessageBody } from './MessageBody'; -import { StateType } from '../../../../state/reducer'; type Props = { messageId: string; @@ -27,13 +27,9 @@ export const MessageText = (props: Props) => { if (!selected) { return null; } - const { text, direction, status, isDeleted, conversationType } = selected; + const { text, isDeleted, conversationType } = selected; - const contents = isDeleted - ? window.i18n('messageDeletedPlaceholder') - : direction === 'incoming' && status === 'error' - ? window.i18n('incomingError') - : text?.trim(); + const contents = isDeleted ? window.i18n('deleteMessageDeleted') : text?.trim(); if (!contents) { return null; diff --git a/ts/components/conversation/message/message-content/Quote.tsx b/ts/components/conversation/message/message-content/Quote.tsx index e2a29d634b..17fbfaf82a 100644 --- a/ts/components/conversation/message/message-content/Quote.tsx +++ b/ts/components/conversation/message/message-content/Quote.tsx @@ -78,7 +78,7 @@ function getTypeLabel({ return window.i18n('photo'); } if (MIME.isAudio(contentType) && isVoiceMessage) { - return window.i18n('voiceMessage'); + return window.i18n('messageVoice'); } if (MIME.isAudio(contentType)) { return window.i18n('audio'); @@ -285,7 +285,7 @@ const QuoteAuthor = (props: QuoteAuthorProps) => { )} > {isFromMe ? ( - window.i18n('you') + window.i18n('onionRoutingPathYou') ) : ( - {window.i18n('originalMessageNotFound')} + {window.i18n('messageErrorOriginal')}
); diff --git a/ts/components/conversation/message/message-item/DataExtractionNotification.tsx b/ts/components/conversation/message/message-item/DataExtractionNotification.tsx index 9d93625fea..7bee4179db 100644 --- a/ts/components/conversation/message/message-item/DataExtractionNotification.tsx +++ b/ts/components/conversation/message/message-item/DataExtractionNotification.tsx @@ -7,12 +7,10 @@ import { NotificationBubble } from './notification-bubble/NotificationBubble'; export const DataExtractionNotification = (props: PropsForDataExtractionNotification) => { const { name, type, source, messageId } = props; - let contentText: string; - if (type === SignalService.DataExtractionNotification.Type.MEDIA_SAVED) { - contentText = window.i18n('savedTheFile', [name || source]); - } else { - contentText = window.i18n('tookAScreenshot', [name || source]); - } + const contentText = + type === SignalService.DataExtractionNotification.Type.MEDIA_SAVED + ? window.i18n('attachmentsMediaSaved', { name: name ?? source }) + : window.i18n('screenshotTaken', { name: name ?? source }); return ( { if (props.direction === 'outgoing') { classes.push('invitation-outgoing'); } - const openGroupInvitation = window.i18n('openGroupInvitation'); + const openGroupInvitation = window.i18n('communityInvitation'); return ( ): string => { throw new Error('Group update add is missing contacts'); } const names = useConversationsUsernameWithQuoteOrFullPubkey(added); - const joinKey = added.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup'; - return window.i18n(joinKey, [names.join(', ')]); + return window.i18n('groupMemberNew', { + name: names.join(', '), + }); }; const ChangeItemKicked = (kicked: Array): string => { @@ -31,8 +32,8 @@ const ChangeItemKicked = (kicked: Array): string => { return window.i18n('youGotKickedFromGroup'); } - const kickedKey = kicked.length > 1 ? 'multipleKickedFromTheGroup' : 'kickedFromTheGroup'; - return window.i18n(kickedKey, [names.join(', ')]); + const kickedKey = kicked.length > 1 ? 'multipleKickedFromTheGroup' : 'groupRemoved'; + return window.i18n(kickedKey, { name: names.join(', ') }); }; const ChangeItemLeft = (left: Array): string => { @@ -43,18 +44,18 @@ const ChangeItemLeft = (left: Array): string => { const names = useConversationsUsernameWithQuoteOrFullPubkey(left); if (arrayContainsUsOnly(left)) { - return window.i18n('youLeftTheGroup'); + return window.i18n('groupMemberYouLeft'); } - const leftKey = left.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup'; - return window.i18n(leftKey, [names.join(', ')]); + const leftKey = left.length > 1 ? 'multipleLeftTheGroup' : 'groupMemberLeft'; + return window.i18n(leftKey, { name: names.join(', ') }); }; const ChangeItem = (change: PropsForGroupUpdateType): string => { const { type } = change; switch (type) { case 'name': - return window.i18n('titleIsNow', [change.newName || '']); + return window.i18n('groupNameNew', { groupname: change.newName }); case 'add': return ChangeItemJoined(change.added); @@ -65,7 +66,7 @@ const ChangeItem = (change: PropsForGroupUpdateType): string => { return ChangeItemKicked(change.kicked); case 'general': - return window.i18n('updatedTheGroup'); + return window.i18n('groupUpdated'); default: assertUnreachable(type, `ChangeItem: Missing case error "${type}"`); return ''; diff --git a/ts/components/conversation/message/message-item/InteractionNotification.tsx b/ts/components/conversation/message/message-item/InteractionNotification.tsx index be06bee1d8..0895bf4c3e 100644 --- a/ts/components/conversation/message/message-item/InteractionNotification.tsx +++ b/ts/components/conversation/message/message-item/InteractionNotification.tsx @@ -42,9 +42,9 @@ export const InteractionNotification = (props: PropsForInteractionNotification) break; case ConversationInteractionType.Leave: text = isCommunity - ? window.i18n('leaveCommunityFailedPleaseTryAgain') + ? window.i18n('communityLeaveError') : isGroup - ? window.i18n('leaveGroupFailedPleaseTryAgain') + ? window.i18n('groupErrorLeave') : window.i18n('deleteConversationFailedPleaseTryAgain'); break; default: diff --git a/ts/components/conversation/message/message-item/MessageRequestResponse.tsx b/ts/components/conversation/message/message-item/MessageRequestResponse.tsx index 4e84b1d7bc..cb8cf84d38 100644 --- a/ts/components/conversation/message/message-item/MessageRequestResponse.tsx +++ b/ts/components/conversation/message/message-item/MessageRequestResponse.tsx @@ -16,10 +16,12 @@ export const MessageRequestResponse = (props: PropsForMessageRequestResponse) => let msgText = ''; if (isFromSync) { msgText = profileName - ? window.i18n('messageRequestAcceptedOurs', [profileName]) + ? window.i18n('messageRequestYouHaveAccepted', { + name: profileName, + }) : window.i18n('messageRequestAcceptedOursNoName'); } else { - msgText = window.i18n('messageRequestAccepted'); + msgText = window.i18n('messageRequestsAccepted'); } return ( diff --git a/ts/components/conversation/right-panel/overlay/disappearing-messages/DisappearingModes.tsx b/ts/components/conversation/right-panel/overlay/disappearing-messages/DisappearingModes.tsx index 083a8733a7..347582fad1 100644 --- a/ts/components/conversation/right-panel/overlay/disappearing-messages/DisappearingModes.tsx +++ b/ts/components/conversation/right-panel/overlay/disappearing-messages/DisappearingModes.tsx @@ -34,7 +34,7 @@ export const DisappearingModes = (props: DisappearingModesProps) => { return ( <> - {window.i18n('disappearingMessagesModeLabel')} + {window.i18n('disappearingMessagesDeleteType')} {Object.keys(options).map(_mode => { const mode = _mode as DisappearingMessageConversationModeType; @@ -42,18 +42,18 @@ export const DisappearingModes = (props: DisappearingModesProps) => { mode === 'legacy' ? window.i18n('disappearingMessagesModeLegacy') : mode === 'deleteAfterRead' - ? window.i18n('disappearingMessagesModeAfterRead') + ? window.i18n('disappearingMessagesDisappearAfterRead') : mode === 'deleteAfterSend' - ? window.i18n('disappearingMessagesModeAfterSend') - : window.i18n('disappearingMessagesModeOff'); + ? window.i18n('disappearingMessagesDisappearAfterSend') + : window.i18n('off'); const subtitleI18n = mode === 'legacy' ? window.i18n('disappearingMessagesModeLegacySubtitle') : mode === 'deleteAfterRead' - ? window.i18n('disappearingMessagesModeAfterReadSubtitle') + ? window.i18n('disappearingMessagesDisappearAfterReadDescription') : mode === 'deleteAfterSend' - ? window.i18n('disappearingMessagesModeAfterSendSubtitle') + ? window.i18n('disappearingMessagesDisappearAfterSendDescription') : undefined; return ( diff --git a/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx b/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx index 2eb668fb08..3c4c0b86ff 100644 --- a/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx +++ b/ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx @@ -174,10 +174,10 @@ export const OverlayDisappearingMessages = () => { {window.i18n('disappearingMessages')} {singleMode === 'deleteAfterRead' - ? window.i18n('disappearingMessagesModeAfterReadSubtitle') + ? window.i18n('disappearingMessagesDisappearAfterReadDescription') : singleMode === 'deleteAfterSend' - ? window.i18n('disappearingMessagesModeAfterSendSubtitle') - : window.i18n('settingAppliesToYourMessages')} + ? window.i18n('disappearingMessagesDisappearAfterSendDescription') + : window.i18n('disappearingMessagesDescription1')} { return ( <> - {!hasOnlyOneMode && {window.i18n('timer')}} + {!hasOnlyOneMode && {window.i18n('disappearingMessagesTimer')}} {options.map(option => { return ( diff --git a/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx b/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx index 496babbebc..bdc047425b 100644 --- a/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx +++ b/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx @@ -283,7 +283,7 @@ export const OverlayMessageInfo = () => { { // eslint-disable-next-line more/no-then diff --git a/ts/components/conversation/right-panel/overlay/message-info/components/AttachmentInfo.tsx b/ts/components/conversation/right-panel/overlay/message-info/components/AttachmentInfo.tsx index 44344b5a64..bfc24884f6 100644 --- a/ts/components/conversation/right-panel/overlay/message-info/components/AttachmentInfo.tsx +++ b/ts/components/conversation/right-panel/overlay/message-info/components/AttachmentInfo.tsx @@ -21,31 +21,31 @@ export const AttachmentInfo = (props: Props) => { return ( diff --git a/ts/components/conversation/right-panel/overlay/message-info/components/MessageInfo.tsx b/ts/components/conversation/right-panel/overlay/message-info/components/MessageInfo.tsx index 1875298f0d..9725265a28 100644 --- a/ts/components/conversation/right-panel/overlay/message-info/components/MessageInfo.tsx +++ b/ts/components/conversation/right-panel/overlay/message-info/components/MessageInfo.tsx @@ -142,7 +142,7 @@ export const MessageInfo = ({ messageId, errors }: { messageId: string; errors: <> ) => { setInputBoxValue(e.target.value?.trim() || ''); @@ -119,7 +119,7 @@ export const BanOrUnBanUserDialog = (props: { await banOrUnBanUser(true); }; - const buttonText = isBan ? i18n('banUser') : i18n('unbanUser'); + const buttonText = isBan ? i18n('banUser') : i18n('banUnbanUser'); return ( diff --git a/ts/components/dialog/DeleteAccountModal.tsx b/ts/components/dialog/DeleteAccountModal.tsx index 9b886b97e2..f0fa0c4e48 100644 --- a/ts/components/dialog/DeleteAccountModal.tsx +++ b/ts/components/dialog/DeleteAccountModal.tsx @@ -87,10 +87,10 @@ async function deleteEverythingAndNetworkData() { window.inboxStore?.dispatch(updateDeleteAccountModal(null)); window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('dialogClearAllDataDeletionFailedTitle'), - message: window.i18n('dialogClearAllDataDeletionFailedDesc'), + title: window.i18n('clearDataError'), + message: window.i18n('clearDataErrorDescriptionGeneric'), okTheme: SessionButtonColor.Danger, - okText: window.i18n('deviceOnly'), + okText: window.i18n('clearDeviceOnly'), onClickOk: async () => { await deleteDbLocally(); window.restart(); @@ -114,13 +114,14 @@ async function deleteEverythingAndNetworkData() { // open a new confirm dialog to ask user what to do window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('dialogClearAllDataDeletionFailedTitle'), - message: window.i18n('dialogClearAllDataDeletionFailedMultiple', [ - potentiallyMaliciousSnodes.join(', '), - ]), - messageSub: window.i18n('dialogClearAllDataDeletionFailedTitleQuestion'), + title: window.i18n('clearDataError'), + message: window.i18n('clearDataErrorDescription2', { + servicenodeid: potentiallyMaliciousSnodes.join(', '), + count: `${potentiallyMaliciousSnodes.length}`, + }), + messageSub: window.i18n('clearDeviceAndNetworkConfirm'), okTheme: SessionButtonColor.Danger, - okText: window.i18n('deviceOnly'), + okText: window.i18n('clearDeviceOnly'), onClickOk: async () => { await deleteDbLocally(); window.restart(); @@ -165,10 +166,8 @@ const DescriptionBeforeAskingConfirmation = (props: { const { deleteMode, setDeleteMode } = props; return ( <> - {window.i18n('deleteAccountWarning')} - - {window.i18n('dialogClearAllDataDeletionQuestion')} - + {window.i18n('clearDataAllDescription')} + {window.i18n('clearDataAllDescription')} ); }; -const DescriptionWhenAskingConfirmation = (props: { deleteMode: DeleteModes }) => { +const DescriptionWhenAskingConfirmation = () => { return ( - {props.deleteMode === 'device_and_network' - ? window.i18n('areYouSureDeleteEntireAccount') - : window.i18n('areYouSureDeleteDeviceOnly')} + {window.i18n('clearDeviceAndNetworkConfirm')} ); }; @@ -243,12 +240,12 @@ export const DeleteAccountModal = () => { return ( {askingConfirmation ? ( - + ) : ( { /* The
element has a child element that allows keyboard interaction */
{ type="text" className="profile-name-input" value={profileName} - placeholder={window.i18n('displayName')} + placeholder={window.i18n('displayNameEnter')} onChange={onNameEdited} maxLength={MAX_USERNAME_BYTES} tabIndex={0} @@ -274,7 +274,7 @@ export const EditProfileDialog = (): ReactElement => { {mode === 'default' || mode === 'qr' ? ( { window.clipboard.writeText(ourId); diff --git a/ts/components/dialog/EditProfilePictureModal.tsx b/ts/components/dialog/EditProfilePictureModal.tsx index 2554c420f3..09d637fd5c 100644 --- a/ts/components/dialog/EditProfilePictureModal.tsx +++ b/ts/components/dialog/EditProfilePictureModal.tsx @@ -111,7 +111,7 @@ export const EditProfilePictureModal = (props: EditProfilePictureModalProps) => return ( { ); } - const chatName = convoProps.displayNameInProfile || window.i18n('unknown'); const isPublicConvo = convoProps.isPublic; const closeDialog = () => { @@ -150,9 +149,7 @@ const InviteContactsDialogInner = (props: Props) => { return event.key === 'Esc' || event.key === 'Escape'; }, closeDialog); - const unknown = window.i18n('unknown'); - - const titleText = `${window.i18n('addingContacts', [chatName || unknown])}`; + const titleText = `${window.i18n('membersInvite')}`; const cancelText = window.i18n('cancel'); const okText = window.i18n('ok'); @@ -177,7 +174,7 @@ const InviteContactsDialogInner = (props: Props) => { ) : ( <> -

{window.i18n('noContactsToAdd')}

+

{window.i18n('contactNone')}

)} diff --git a/ts/components/dialog/ModeratorsAddDialog.tsx b/ts/components/dialog/ModeratorsAddDialog.tsx index b8b3b3b35d..15c77c5554 100644 --- a/ts/components/dialog/ModeratorsAddDialog.tsx +++ b/ts/components/dialog/ModeratorsAddDialog.tsx @@ -86,7 +86,7 @@ export const AddModeratorsDialog = (props: Props) => { { const nodes = [ { - label: window.i18n('device'), + label: window.i18n('onionRoutingPathYou'), }, ...onionPath, { - label: window.i18n('destination'), + label: window.i18n('onionRoutingPathDestination'), }, ]; return ( <> - - {window.i18n('onionPathIndicatorDescription')} - + {window.i18n('onionRoutingPathDescription')} @@ -123,7 +121,7 @@ const OnionPathModalInner = () => { ? snode.label : countryLookup.byIso(ip2country(snode.ip))?.country; if (!labelText) { - labelText = window.i18n('unknownCountry'); + labelText = window.i18n('onionRoutingPathUnknownCountry'); } return labelText ? ( { const dispatch = useDispatch(); return ( { onClose={handleClose} > -

{window.i18n('clearAllReactions', [reaction])}

+

{window.i18n('emojiReactsClearAll', { emoji: reaction })}

{ }} /> {sender === me ? ( - window.i18n('you') + window.i18n('onionRoutingPathYou') ) : ( { Reactions.SOGSReactorsFetchCount + 1 - ? window.i18n('reactionListCountPlural', [ - window.i18n('otherPlural', [String(count - Reactions.SOGSReactorsFetchCount)]), + ? window.i18n('reactionListCountPlural', { + otherPlural: window.i18n('otherPlural', { + number: String(count - Reactions.SOGSReactorsFetchCount), + }), emoji, - ]) - : window.i18n('reactionListCountSingular', [ - window.i18n('otherSingular', [String(count - Reactions.SOGSReactorsFetchCount)]), + }) + : window.i18n('reactionListCountSingular', { + otherSingular: window.i18n('otherSingular', { + number: String(count - Reactions.SOGSReactorsFetchCount), + }), emoji, - ]) + }) } /> diff --git a/ts/components/dialog/SessionNicknameDialog.tsx b/ts/components/dialog/SessionNicknameDialog.tsx index 008d341854..d2d7512978 100644 --- a/ts/components/dialog/SessionNicknameDialog.tsx +++ b/ts/components/dialog/SessionNicknameDialog.tsx @@ -50,13 +50,13 @@ export const SessionNicknameDialog = (props: Props) => { return (
- {window.i18n('changeNicknameMessage')} + {window.i18n('nicknameEnter')}
@@ -64,7 +64,7 @@ export const SessionNicknameDialog = (props: Props) => { autoFocus={true} type="nickname" id="nickname-modal-input" - placeholder={window.i18n('nicknamePlaceholder')} + placeholder={window.i18n('nicknameSet')} onKeyUp={e => { void onNicknameInput(_.cloneDeep(e)); }} diff --git a/ts/components/dialog/SessionPasswordDialog.tsx b/ts/components/dialog/SessionPasswordDialog.tsx index 1eafcf5382..112f5d232e 100644 --- a/ts/components/dialog/SessionPasswordDialog.tsx +++ b/ts/components/dialog/SessionPasswordDialog.tsx @@ -6,7 +6,7 @@ import { ToastUtils } from '../../session/utils'; import { Data } from '../../data/data'; import { SpacerSM } from '../basic/Text'; import { sessionPassword } from '../../state/ducks/modalDialog'; -import { LocalizerKeys } from '../../types/LocalizerKeys'; +import { LocalizerToken } from '../../types/Localizer'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton'; import { SessionWrapperModal } from '../SessionWrapperModal'; import { matchesHash, validatePassword } from '../../util/passwordUtils'; @@ -60,32 +60,32 @@ export class SessionPasswordDialog extends React.Component { switch (passwordAction) { case 'change': placeholders = [ - window.i18n('typeInOldPassword'), - window.i18n('enterNewPassword'), - window.i18n('confirmNewPassword'), + window.i18n('passwordEnterCurrent'), + window.i18n('passwordEnterNew'), + window.i18n('passwordConfirm'), ]; break; case 'remove': - placeholders = [window.i18n('enterPassword')]; + placeholders = [window.i18n('passwordCreate')]; break; case 'enter': - placeholders = [window.i18n('enterPassword')]; + placeholders = [window.i18n('passwordCreate')]; break; default: - placeholders = [window.i18n('createPassword'), window.i18n('confirmPassword')]; + placeholders = [window.i18n('createPassword'), window.i18n('passwordConfirm')]; } const confirmButtonText = passwordAction === 'remove' ? window.i18n('remove') : window.i18n('done'); // do this separately so typescript's compiler likes it - const localizedKeyAction: LocalizerKeys = + const localizedKeyAction: LocalizerToken = passwordAction === 'change' - ? 'changePassword' + ? 'passwordChange' : passwordAction === 'remove' - ? 'removePassword' + ? 'passwordRemove' : passwordAction === 'enter' - ? 'passwordViewTitle' - : 'setPassword'; + ? 'passwordEnter' + : 'passwordSet'; return ( @@ -189,7 +189,7 @@ export class SessionPasswordDialog extends React.Component { if (enteredPassword !== enteredPasswordConfirm) { this.setState({ - error: window.i18n('setPasswordInvalid'), + error: window.i18n('passwordErrorMatch'), }); this.showError(); return; @@ -197,8 +197,8 @@ export class SessionPasswordDialog extends React.Component { await window.setPassword(enteredPassword, null); ToastUtils.pushToastSuccess( 'setPasswordSuccessToast', - window.i18n('setPasswordTitle'), - window.i18n('setPasswordToastDescription') + window.i18n('passwordSet'), + window.i18n('passwordSetDescription') ); this.props.onOk(); @@ -219,7 +219,7 @@ export class SessionPasswordDialog extends React.Component { // Check the retyped password matches the new password if (newPassword !== newConfirmedPassword) { this.setState({ - error: window.i18n('passwordsDoNotMatch'), + error: window.i18n('passwordErrorMatch'), }); this.showError(); return; @@ -228,7 +228,7 @@ export class SessionPasswordDialog extends React.Component { const isValidWithStoredInDB = Boolean(await this.validatePasswordHash(oldPassword)); if (!isValidWithStoredInDB) { this.setState({ - error: window.i18n('changePasswordInvalid'), + error: window.i18n('passwordCurrentIncorrect'), }); this.showError(); return; @@ -237,8 +237,7 @@ export class SessionPasswordDialog extends React.Component { ToastUtils.pushToastSuccess( 'setPasswordSuccessToast', - window.i18n('changePasswordTitle'), - window.i18n('changePasswordToastDescription') + window.i18n('passwordChangedDescription') ); this.props.onOk(); @@ -250,7 +249,7 @@ export class SessionPasswordDialog extends React.Component { const isValidWithStoredInDB = Boolean(await this.validatePasswordHash(oldPassword)); if (!isValidWithStoredInDB) { this.setState({ - error: window.i18n('removePasswordInvalid'), + error: window.i18n('passwordIncorrect'), }); this.showError(); return; @@ -259,8 +258,7 @@ export class SessionPasswordDialog extends React.Component { ToastUtils.pushToastWarning( 'setPasswordSuccessToast', - window.i18n('removePasswordTitle'), - window.i18n('removePasswordToastDescription') + window.i18n('passwordRemovedDescription') ); this.props.onOk(); @@ -283,7 +281,7 @@ export class SessionPasswordDialog extends React.Component { const isValidWithStoredInDB = Boolean(await this.validatePasswordHash(enteredPassword)); if (!isValidWithStoredInDB) { this.setState({ - error: window.i18n('invalidPassword'), + error: window.i18n('passwordIncorrect'), }); this.showError(); return; diff --git a/ts/components/dialog/SessionSeedModal.tsx b/ts/components/dialog/SessionSeedModal.tsx index ba6eba3a08..a5bda39a5f 100644 --- a/ts/components/dialog/SessionSeedModal.tsx +++ b/ts/components/dialog/SessionSeedModal.tsx @@ -34,13 +34,13 @@ const Password = (props: PasswordProps) => { const isPasswordValid = matchesHash(passwordValue as string, passwordHash); if (!passwordValue) { - ToastUtils.pushToastError('enterPasswordErrorToast', i18n('noGivenPassword')); + ToastUtils.pushToastError('enterPasswordErrorToast', i18n('passwordCreate')); return false; } if (passwordHash && !isPasswordValid) { - ToastUtils.pushToastError('enterPasswordErrorToast', i18n('invalidPassword')); + ToastUtils.pushToastError('enterPasswordErrorToast', i18n('passwordIncorrect')); return false; } @@ -62,7 +62,7 @@ const Password = (props: PasswordProps) => {
@@ -143,6 +143,7 @@ const Seed = (props: SeedProps) => { maxWidth: '600px', }} > + {/** TODO: String localization - remove */} {i18n('recoveryPhraseSavePromptMain')}

@@ -167,7 +168,7 @@ const Seed = (props: SeedProps) => { style={{ justifyContent: 'center', width: '100%' }} > { copyRecoveryPhrase(recoveryPhrase); @@ -229,7 +230,7 @@ const SessionSeedModalInner = (props: ModalInnerProps) => { <> {!loadingSeed && ( diff --git a/ts/components/dialog/UpdateGroupMembersDialog.tsx b/ts/components/dialog/UpdateGroupMembersDialog.tsx index 070a1c2b05..2cdae15284 100644 --- a/ts/components/dialog/UpdateGroupMembersDialog.tsx +++ b/ts/components/dialog/UpdateGroupMembersDialog.tsx @@ -100,6 +100,7 @@ const ZombiesList = ({ convoId }: { convoId: string }) => { {weAreAdmin && ( { const showNoMembersMessage = existingMembers.length === 0; const okText = window.i18n('ok'); const cancelText = window.i18n('cancel'); - const titleText = window.i18n('updateGroupDialogTitle', [convoProps.displayNameInProfile || '']); + // TODO: String localization - remove + const titleText = window.i18n('updateGroupDialogTitle', { + name: convoProps.displayNameInProfile ?? '', + }); return ( @@ -241,7 +245,7 @@ export const UpdateGroupMembersDialog = (props: Props) => { /> - {showNoMembersMessage &&

{window.i18n('noMembersInThisGroup')}

} + {showNoMembersMessage &&

{window.i18n('groupMembersNone')}

} diff --git a/ts/components/dialog/UpdateGroupNameDialog.tsx b/ts/components/dialog/UpdateGroupNameDialog.tsx index 63b59276fc..e14f88befa 100644 --- a/ts/components/dialog/UpdateGroupNameDialog.tsx +++ b/ts/components/dialog/UpdateGroupNameDialog.tsx @@ -56,7 +56,7 @@ export class UpdateGroupNameDialog extends React.Component { const { groupName, newAvatarObjecturl, oldAvatarPath } = this.state; const trimmedGroupName = groupName?.trim(); if (!trimmedGroupName) { - this.onShowError(window.i18n('emptyGroupNameError')); + this.onShowError(window.i18n('groupNameEnterPlease')); return; } @@ -82,9 +82,10 @@ export class UpdateGroupNameDialog extends React.Component { public render() { const okText = window.i18n('ok'); const cancelText = window.i18n('cancel'); - const titleText = window.i18n('updateGroupDialogTitle', [ - this.convo.getRealSessionUsername() || window.i18n('unknown'), - ]); + // TODO: String localization - remove + const titleText = window.i18n('updateGroupDialogTitle', { + name: this.convo.getRealSessionUsername() ?? window.i18n('unknown'), + }); const errorMsg = this.state.errorMessage; const errorMessageClasses = classNames( @@ -116,7 +117,7 @@ export class UpdateGroupNameDialog extends React.Component { type="text" className="profile-name-input" value={this.state.groupName} - placeholder={window.i18n('groupNamePlaceholder')} + placeholder={window.i18n('groupName')} onChange={this.onGroupNameChanged} tabIndex={0} required={true} diff --git a/ts/components/dialog/UserDetailsDialog.tsx b/ts/components/dialog/UserDetailsDialog.tsx index 87be42e49a..a964ec940d 100644 --- a/ts/components/dialog/UserDetailsDialog.tsx +++ b/ts/components/dialog/UserDetailsDialog.tsx @@ -74,12 +74,12 @@ export const UserDetailsDialog = (props: UserDetailsModalState) => {
{ copyToClipboard(props.conversationId); diff --git a/ts/components/leftpane/ContactListItem.tsx b/ts/components/leftpane/ContactListItem.tsx index 818bfc4ded..20f062cb44 100644 --- a/ts/components/leftpane/ContactListItem.tsx +++ b/ts/components/leftpane/ContactListItem.tsx @@ -23,7 +23,7 @@ export const ContactListItem = (props: Props) => { const isGroup = !useIsPrivate(pubkey); const title = name || pubkey; - const displayName = isMe ? window.i18n('you') : title; + const displayName = isMe ? window.i18n('onionRoutingPathYou') : title; return (
{ return ( + {/** TODO: String localization - remove */}

{window.i18n('recoveryPhraseRevealMessage')}

{ + {/** TODO: String localization - remove */} {window.i18n('recoveryPhraseSecureTitle')} 90% @@ -118,12 +121,12 @@ export const LeftPaneSectionHeader = () => { switch (focusedSection) { case SectionType.Settings: - label = window.i18n('settingsHeader'); + label = window.i18n('sessionSettings'); break; case SectionType.Message: label = isMessageRequestOverlayShown - ? window.i18n('messageRequests') - : window.i18n('messagesHeader'); + ? window.i18n('sessionMessageRequests') + : window.i18n('messages'); break; default: } diff --git a/ts/components/leftpane/LeftPaneSettingSection.tsx b/ts/components/leftpane/LeftPaneSettingSection.tsx index b2dd84315b..2928934e83 100644 --- a/ts/components/leftpane/LeftPaneSettingSection.tsx +++ b/ts/components/leftpane/LeftPaneSettingSection.tsx @@ -46,15 +46,15 @@ const getCategories = () => { return [ { id: SessionSettingCategory.Privacy, - title: window.i18n('privacySettingsTitle'), + title: window.i18n('sessionPrivacy'), }, { id: SessionSettingCategory.Notifications, - title: window.i18n('notificationsSettingsTitle'), + title: window.i18n('sessionNotifications'), }, { id: SessionSettingCategory.Conversations, - title: window.i18n('conversationsSettingsTitle'), + title: window.i18n('sessionConversations'), }, { id: SessionSettingCategory.MessageRequests, @@ -62,23 +62,23 @@ const getCategories = () => { }, { id: SessionSettingCategory.Appearance, - title: window.i18n('appearanceSettingsTitle'), + title: window.i18n('sessionAppearance'), }, { id: SessionSettingCategory.Permissions, - title: window.i18n('permissionsSettingsTitle'), + title: window.i18n('sessionPermissions'), }, { id: SessionSettingCategory.Help, - title: window.i18n('helpSettingsTitle'), + title: window.i18n('sessionHelp'), }, { id: SessionSettingCategory.RecoveryPhrase, - title: window.i18n('recoveryPhrase'), + title: window.i18n('sessionRecoveryPassword'), }, { id: SessionSettingCategory.ClearData, - title: window.i18n('clearDataSettingsTitle'), + title: window.i18n('sessionClearData'), }, ]; }; diff --git a/ts/components/leftpane/MessageRequestsBanner.tsx b/ts/components/leftpane/MessageRequestsBanner.tsx index 8f06088d35..4681000aff 100644 --- a/ts/components/leftpane/MessageRequestsBanner.tsx +++ b/ts/components/leftpane/MessageRequestsBanner.tsx @@ -123,7 +123,7 @@ export const MessageRequestsBanner = (props: { handleOnClick: () => any }) => { > - {window.i18n('messageRequests')} + {window.i18n('sessionMessageRequests')}
{conversationRequestsUnread || 0}
diff --git a/ts/components/leftpane/conversation-list-item/InteractionItem.tsx b/ts/components/leftpane/conversation-list-item/InteractionItem.tsx index 0e7b0031f4..2c95df2c9d 100644 --- a/ts/components/leftpane/conversation-list-item/InteractionItem.tsx +++ b/ts/components/leftpane/conversation-list-item/InteractionItem.tsx @@ -64,7 +64,7 @@ export const InteractionItem = (props: InteractionItemProps) => { errorText = isCommunity ? window.i18n('leaveCommunityFailed') : isGroup - ? window.i18n('leaveGroupFailed') + ? window.i18n('groupErrorLeave') : window.i18n('deleteConversationFailed'); text = interactionStatus === ConversationInteractionStatus.Error diff --git a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx index f141f38099..b4e3060bce 100644 --- a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx +++ b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx @@ -48,7 +48,7 @@ const StyledGroupMemberList = styled.div` const NoContacts = () => { return ( - {window.i18n('noContactsForGroup')} + {window.i18n('conversationsNone')} ); }; @@ -61,12 +61,12 @@ async function createClosedGroupWithToasts( ): Promise { // Validate groupName and groupMembers length if (groupName.length === 0) { - ToastUtils.pushToastError('invalidGroupName', window.i18n('invalidGroupNameTooShort')); + ToastUtils.pushToastError('invalidGroupName', window.i18n('groupNameEnterPlease')); return false; } if (groupName.length > VALIDATION.MAX_GROUP_NAME_LENGTH) { - ToastUtils.pushToastError('invalidGroupName', window.i18n('invalidGroupNameTooLong')); + ToastUtils.pushToastError('invalidGroupName', window.i18n('groupNameEnterShorter')); return false; } @@ -74,11 +74,11 @@ async function createClosedGroupWithToasts( // the same is valid with groups count < 1 if (groupMemberIds.length < 1) { - ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('pickClosedGroupMember')); + ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('groupCreateErrorNoMembers')); return false; } if (groupMemberIds.length >= VALIDATION.CLOSED_GROUP_SIZE_LIMIT) { - ToastUtils.pushToastError('closedGroupMaxSize', window.i18n('closedGroupMaxSize')); + ToastUtils.pushToastError('closedGroupMaxSize', window.i18n('groupAddMemberMaximum')); return false; } @@ -120,10 +120,10 @@ export const OverlayClosedGroup = () => { useKey('Escape', closeOverlay); - const title = window.i18n('createGroup'); + const title = window.i18n('groupCreate'); const buttonText = window.i18n('create'); - const subtitle = window.i18n('createClosedGroupNamePrompt'); - const placeholder = window.i18n('createClosedGroupPlaceholder'); + const subtitle = window.i18n('groupName'); + const placeholder = window.i18n('groupNameEnter'); const noContactsForClosedGroup = privateContactsPubkeys.length === 0; diff --git a/ts/components/leftpane/overlay/OverlayCommunity.tsx b/ts/components/leftpane/overlay/OverlayCommunity.tsx index e97210c092..c9edcf6ec3 100644 --- a/ts/components/leftpane/overlay/OverlayCommunity.tsx +++ b/ts/components/leftpane/overlay/OverlayCommunity.tsx @@ -79,10 +79,10 @@ export const OverlayCommunity = () => { useKey('Escape', closeOverlay); - const title = window.i18n('joinOpenGroup'); + const title = window.i18n('communityJoin'); const buttonText = window.i18n('join'); - const subtitle = window.i18n('openGroupURL'); - const placeholder = window.i18n('enterAnOpenGroupURL'); + const subtitle = window.i18n('communityUrl'); + const placeholder = window.i18n('communityEnterUrl'); return (
diff --git a/ts/components/leftpane/overlay/OverlayMessage.tsx b/ts/components/leftpane/overlay/OverlayMessage.tsx index 59f1135744..528816d3a2 100644 --- a/ts/components/leftpane/overlay/OverlayMessage.tsx +++ b/ts/components/leftpane/overlay/OverlayMessage.tsx @@ -49,10 +49,10 @@ export const OverlayMessage = () => { const [pubkeyOrOns, setPubkeyOrOns] = useState(''); const [loading, setLoading] = useState(false); - const title = window.i18n('newMessage'); + const title = window.i18n('messageNew'); const buttonText = window.i18n('next'); - const subtitle = window.i18n('enterSessionID'); - const placeholder = window.i18n('enterSessionIDOrONSName'); + const subtitle = window.i18n('accountIdEnter'); + const placeholder = window.i18n('accountIdEnterYourFriends'); const disableNextButton = !pubkeyOrOns || loading; @@ -80,7 +80,7 @@ export const OverlayMessage = () => { async function handleMessageButtonClick() { if ((!pubkeyOrOns && !pubkeyOrOns.length) || !pubkeyOrOns.trim().length) { - ToastUtils.pushToastError('invalidPubKey', window.i18n('invalidNumberError')); // or ons name + ToastUtils.pushToastError('invalidPubKey', window.i18n('onsErrorNotRecognised')); // or ons name return; } const pubkeyorOnsTrimmed = pubkeyOrOns.trim(); @@ -93,7 +93,7 @@ export const OverlayMessage = () => { // this might be an ONS, validate the regex first const mightBeOnsName = new RegExp(ONSResolve.onsNameRegex, 'g').test(pubkeyorOnsTrimmed); if (!mightBeOnsName) { - ToastUtils.pushToastError('invalidPubKey', window.i18n('invalidNumberError')); + ToastUtils.pushToastError('invalidPubKey', window.i18n('onsErrorNotRecognised')); return; } setLoading(true); @@ -106,7 +106,7 @@ export const OverlayMessage = () => { await openConvoOnceResolved(resolvedSessionID); } catch (e) { window?.log?.warn('failed to resolve ons name', pubkeyorOnsTrimmed, e); - ToastUtils.pushToastError('invalidPubKey', window.i18n('failedResolveOns')); + ToastUtils.pushToastError('invalidPubKey', window.i18n('onsErrorUnableToSearch')); } finally { setLoading(false); } diff --git a/ts/components/leftpane/overlay/OverlayMessageRequest.tsx b/ts/components/leftpane/overlay/OverlayMessageRequest.tsx index a8afda3e2b..4049a474f8 100644 --- a/ts/components/leftpane/overlay/OverlayMessageRequest.tsx +++ b/ts/components/leftpane/overlay/OverlayMessageRequest.tsx @@ -58,8 +58,8 @@ export const OverlayMessageRequest = () => { */ function handleClearAllRequestsClick() { const { i18n } = window; - const title = i18n('clearAllConfirmationTitle'); - const message = i18n('clearAllConfirmationBody'); + const title = i18n('clearAll'); + const message = i18n('messageRequestsClearAllExplanation'); const onClose = dispatch(updateConfirmModal(null)); dispatch( @@ -110,7 +110,7 @@ export const OverlayMessageRequest = () => { <> - {window.i18n('noMessageRequestsPending')} + {window.i18n('messageRequestsNonePending')} )} diff --git a/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx b/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx index 3325213867..e37a05623a 100644 --- a/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx +++ b/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx @@ -147,7 +147,7 @@ export const SessionJoinableRooms = (props: { return ( -

+

{joinableRooms.inProgress ? ( diff --git a/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx b/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx index 1fd71411c7..5ebf39a35f 100644 --- a/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx +++ b/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx @@ -115,7 +115,7 @@ const ContactsTitle = () => { } return ( - {window.i18n('contactsHeader')} + {window.i18n('contactContacts')} ); }; diff --git a/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx b/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx index 210e512a25..ad79babdfd 100644 --- a/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx +++ b/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx @@ -93,7 +93,7 @@ export const OverlayChooseAction = () => { aria-label={window.i18n('createConversationNewContact')} > - {window.i18n('newMessage')} + {window.i18n('messageNew')} { aria-label={window.i18n('createConversationNewGroup')} > - {window.i18n('createGroup')} + {window.i18n('groupCreate')} { aria-label={window.i18n('joinACommunity')} > - {window.i18n('joinOpenGroup')} + {window.i18n('communityJoin')}

diff --git a/ts/components/menu/ConversationListItemContextMenu.tsx b/ts/components/menu/ConversationListItemContextMenu.tsx index 9e3e0680b0..fde52c3102 100644 --- a/ts/components/menu/ConversationListItemContextMenu.tsx +++ b/ts/components/menu/ConversationListItemContextMenu.tsx @@ -88,7 +88,9 @@ export const PinConversationMenuItem = (): JSX.Element | null => { void conversation?.togglePinned(); }; - const menuText = isPinned ? window.i18n('unpinConversation') : window.i18n('pinConversation'); + const menuText = isPinned + ? window.i18n('pinUnpinConversation') + : window.i18n('pinConversation'); return {menuText}; } return null; diff --git a/ts/components/menu/Menu.tsx b/ts/components/menu/Menu.tsx index 4650125940..b789f58402 100644 --- a/ts/components/menu/Menu.tsx +++ b/ts/components/menu/Menu.tsx @@ -54,7 +54,7 @@ import { } from '../../state/ducks/modalDialog'; import { getIsMessageSection } from '../../state/selectors/section'; import { useSelectedConversationKey } from '../../state/selectors/selectedConversation'; -import { LocalizerKeys } from '../../types/LocalizerKeys'; +import { LocalizerToken } from '../../types/Localizer'; import { SessionButtonColor } from '../basic/SessionButton'; import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext'; @@ -71,7 +71,7 @@ export const InviteContactMenuItem = (): JSX.Element | null => { showInviteContactByConvoId(convoId); }} > - {window.i18n('inviteContacts')} + {window.i18n('membersInvite')} ); } @@ -91,7 +91,7 @@ export const MarkConversationUnreadMenuItem = (): JSX.Element | null => { void conversation?.markAsUnread(true); }; - return {window.i18n('markUnread')}; + return {window.i18n('messageMarkUnread')}; } return null; }; @@ -108,7 +108,7 @@ export const DeletePrivateContactMenuItem = () => { const isRequest = useIsIncomingRequest(convoId); if (isPrivate && !isRequest) { - const menuItemText = window.i18n('editMenuDeleteContact'); + const menuItemText = window.i18n('contactDelete'); const onClickClose = () => { dispatch(updateConfirmModal(null)); @@ -153,11 +153,11 @@ export const LeaveGroupOrCommunityMenuItem = () => { }} > {isPublic - ? window.i18n('leaveCommunity') + ? window.i18n('communityLeave') : lastMessage?.interactionType === ConversationInteractionType.Leave && lastMessage?.interactionStatus === ConversationInteractionStatus.Error - ? window.i18n('deleteConversation') - : window.i18n('leaveGroup')} + ? window.i18n('conversationsDelete') + : window.i18n('groupLeave')} ); } @@ -186,7 +186,7 @@ export const ShowUserDetailsMenuItem = () => { ); }} > - {window.i18n('showUserDetails')} + {window.i18n('contactUserDetails')} ); } @@ -207,7 +207,7 @@ export const UpdateGroupNameMenuItem = () => { void showUpdateGroupNameByConvoId(convoId); }} > - {window.i18n('editGroup')} + {window.i18n('groupEdit')} ); } @@ -267,7 +267,7 @@ export const UnbanMenuItem = (): JSX.Element | null => { showUnbanUserByConvoId(convoId); }} > - {window.i18n('unbanUser')} + {window.i18n('banUnbanUser')} ); } @@ -303,7 +303,7 @@ export const CopyMenuItem = (): JSX.Element | null => { // we want to show the copyId for open groups and private chats only if ((isPrivate && !isBlinded) || isPublic) { - const copyIdLabel = isPublic ? window.i18n('copyOpenGroupURL') : window.i18n('copySessionID'); + const copyIdLabel = isPublic ? window.i18n('communityUrlCopy') : window.i18n('accountIDCopy'); return ( { @@ -324,7 +324,7 @@ export const MarkAllReadMenuItem = (): JSX.Element | null => { return ( // eslint-disable-next-line @typescript-eslint/no-misused-promises markAllReadByConvoId(convoId)}> - {window.i18n('markAllAsRead')} + {window.i18n('messageMarkRead')} ); } @@ -339,7 +339,7 @@ export const BlockMenuItem = (): JSX.Element | null => { const isIncomingRequest = useIsIncomingRequest(convoId); if (!isMe && isPrivate && !isIncomingRequest && !PubKey.isBlinded(convoId)) { - const blockTitle = isBlocked ? window.i18n('unblock') : window.i18n('block'); + const blockTitle = isBlocked ? window.i18n('blockUnblock') : window.i18n('block'); const blockHandler = isBlocked ? async () => unblockConvoById(convoId) : async () => blockConvoById(convoId); @@ -363,7 +363,7 @@ export const ClearNicknameMenuItem = (): JSX.Element | null => { return ( // eslint-disable-next-line @typescript-eslint/no-misused-promises clearNickNameByConvoId(convoId)}> - {window.i18n('clearNickname')} + {window.i18n('nicknameRemove')} ); }; @@ -384,7 +384,7 @@ export const ChangeNicknameMenuItem = () => { dispatch(changeNickNameModal({ conversationId: convoId })); }} > - {window.i18n('changeNickname')} + {window.i18n('nicknameSet')} ); }; @@ -418,7 +418,6 @@ export const DeleteMessagesMenuItem = () => { */ export const DeletePrivateConversationMenuItem = () => { const convoId = useConvoIdFromContext(); - const username = useConversationUsername(convoId) || convoId; const isRequest = useIsIncomingRequest(convoId); const isPrivate = useIsPrivate(convoId); const isMe = useIsMe(convoId); @@ -430,10 +429,10 @@ export const DeletePrivateConversationMenuItem = () => { return ( { - showLeavePrivateConversationbyConvoId(convoId, username); + showLeavePrivateConversationbyConvoId(convoId); }} > - {isMe ? window.i18n('hideConversation') : window.i18n('deleteConversation')} + {isMe ? window.i18n('noteToSelfHide') : window.i18n('conversationsDelete')} ); }; @@ -542,19 +541,19 @@ export const NotificationForConvoMenuItem = (): JSX.Element | null => { isPrivate ? n !== 'mentions_only' : true ).map((n: ConversationNotificationSettingType) => { // do this separately so typescript's compiler likes it - const keyToUse: LocalizerKeys = + const keyToUse: LocalizerToken = n === 'all' || !n - ? 'notificationForConvo_all' + ? 'notificationsAllMessages' : n === 'disabled' - ? 'notificationForConvo_disabled' - : 'notificationForConvo_mentions_only'; + ? 'notificationsMute' + : 'notificationsMentionsOnly'; return { value: n, name: window.i18n(keyToUse) }; }); return ( // Remove the && false to make context menu work with RTL support {(notificationForConvoOptions || []).map(item => { diff --git a/ts/components/menu/MessageRequestBannerContextMenu.tsx b/ts/components/menu/MessageRequestBannerContextMenu.tsx index a8ef676398..1439286774 100644 --- a/ts/components/menu/MessageRequestBannerContextMenu.tsx +++ b/ts/components/menu/MessageRequestBannerContextMenu.tsx @@ -18,7 +18,7 @@ const HideBannerMenuItem = (): JSX.Element => { dispatch(hideMessageRequestBanner()); }} > - {window.i18n('hideBanner')} + {window.i18n('hide')} ); }; diff --git a/ts/components/registration/RegistrationStages.tsx b/ts/components/registration/RegistrationStages.tsx index fe3e196d62..d6140746e3 100644 --- a/ts/components/registration/RegistrationStages.tsx +++ b/ts/components/registration/RegistrationStages.tsx @@ -36,7 +36,7 @@ const displayNameIsValid = (displayName: string): undefined | string => { if (!trimName) { window?.log?.warn('invalid trimmed name for registration'); - ToastUtils.pushToastError('invalidDisplayName', window.i18n('displayNameEmpty')); + ToastUtils.pushToastError('invalidDisplayName', window.i18n('displayNameErrorDescription')); return undefined; } return trimName; diff --git a/ts/components/registration/RegistrationUserDetails.tsx b/ts/components/registration/RegistrationUserDetails.tsx index ab17a76e02..303844169a 100644 --- a/ts/components/registration/RegistrationUserDetails.tsx +++ b/ts/components/registration/RegistrationUserDetails.tsx @@ -12,9 +12,9 @@ const DisplayNameInput = (props: { return ( { return ( any }) => { return ( ); @@ -35,7 +35,7 @@ const RestoreUsingRecoveryPhraseButton = (props: { onRecoveryButtonClicked: () = return ( ); @@ -48,7 +48,7 @@ const ContinueYourSessionButton = (props: { return ( @@ -97,7 +97,7 @@ export function sanitizeDisplayNameOrToast( const sanitizedName = sanitizeSessionUsername(displayName); const trimName = sanitizedName.trim(); setDisplayName(sanitizedName); - setDisplayNameError(!trimName ? window.i18n('displayNameEmpty') : undefined); + setDisplayNameError(!trimName ? window.i18n('displayNameErrorDescription') : undefined); } catch (e) { setDisplayName(displayName); setDisplayNameError(window.i18n('displayNameTooLong')); @@ -162,7 +162,7 @@ export const SignInTab = () => { }} onSeedChanged={(seed: string) => { setRecoveryPhrase(seed); - setRecoveryPhraseError(!seed ? window.i18n('recoveryPhraseEmpty') : undefined); + setRecoveryPhraseError(!seed ? window.i18n('recoveryPasswordEnter') : undefined); }} recoveryPhrase={recoveryPhrase} stealAutoFocus={true} diff --git a/ts/components/registration/SignUpTab.tsx b/ts/components/registration/SignUpTab.tsx index a54d0a01c3..460d3f0c8d 100644 --- a/ts/components/registration/SignUpTab.tsx +++ b/ts/components/registration/SignUpTab.tsx @@ -16,7 +16,7 @@ export enum SignUpMode { } const CreateSessionIdButton = ({ createSessionID }: { createSessionID: any }) => { - return ; + return ; }; const ContinueSignUpButton = ({ continueSignUp }: { continueSignUp: any }) => { @@ -54,10 +54,12 @@ const SignUpSessionIDShown = (props: { continueSignUp: Noop }) => {
+ {/** TODO: String localization - remove */} {window.i18n('yourUniqueSessionID')}
+ {/** TODO: String localization - remove */}
{window.i18n('allUsersAreRandomly...')}
@@ -121,6 +123,7 @@ export const SignUpTab = () => { + {/** TODO: String localization - remove */} {window.i18n('welcomeToYourSession')} @@ -136,6 +139,7 @@ export const SignUpTab = () => { /> diff --git a/ts/components/search/MessageSearchResults.tsx b/ts/components/search/MessageSearchResults.tsx index 13ac830988..ec29c5ebd8 100644 --- a/ts/components/search/MessageSearchResults.tsx +++ b/ts/components/search/MessageSearchResults.tsx @@ -82,7 +82,11 @@ const FromName = (props: { source: string; conversationId: string }) => { } if (source === getOurPubKeyStrFromCache()) { - return {window.i18n('you')}; + return ( + + {window.i18n('onionRoutingPathYou')} + + ); } return ( @@ -129,7 +133,7 @@ const FromUserInGroup = (props: { authorPubkey: string; conversationId: string } if (authorPubkey === ourKey) { return ( - {window.i18n('you')}: + {window.i18n('onionRoutingPathYou')}: ); } return {authorConvoName}: ; diff --git a/ts/components/search/SearchResults.tsx b/ts/components/search/SearchResults.tsx index b33d185f4a..369fa9fb33 100644 --- a/ts/components/search/SearchResults.tsx +++ b/ts/components/search/SearchResults.tsx @@ -83,13 +83,13 @@ const VirtualizedList = () => { }; export const SearchResults = () => { - const searchTerm = useSelector(getSearchTerm); + const query = useSelector(getSearchTerm); const hasSearchResults = useSelector(getHasSearchResults); return ( {!hasSearchResults ? ( - {window.i18n('noSearchResults', [searchTerm])} + {window.i18n('searchMatchesNoneSpecific', { query })} ) : ( )} diff --git a/ts/components/settings/BlockedList.tsx b/ts/components/settings/BlockedList.tsx index 10b622b53a..57b7c40eb0 100644 --- a/ts/components/settings/BlockedList.tsx +++ b/ts/components/settings/BlockedList.tsx @@ -87,7 +87,7 @@ const BlockedEntries = (props: { }; const NoBlockedContacts = () => { - return
{window.i18n('noBlockedContacts')}
; + return
{window.i18n('blockBlockedNone')}
; }; export const BlockedContactsList = () => { @@ -115,7 +115,12 @@ export const BlockedContactsList = () => { if (selectedIds.length) { await BlockedNumberController.unblockAll(selectedIds); emptySelected(); - ToastUtils.pushToastSuccess('unblocked', window.i18n('unblocked')); + ToastUtils.pushToastSuccess( + 'unblocked', + window.i18n('blockUnblockedUser', { + name: selectedIds.join(', '), + }) + ); forceUpdate(); } } @@ -124,7 +129,7 @@ export const BlockedContactsList = () => { - + {noBlockedNumbers ? ( ) : ( @@ -132,7 +137,7 @@ export const BlockedContactsList = () => { {hasAtLeastOneSelected && expanded ? ( diff --git a/ts/components/settings/SessionNotificationGroupSettings.tsx b/ts/components/settings/SessionNotificationGroupSettings.tsx index 1fd73d430c..918f554534 100644 --- a/ts/components/settings/SessionNotificationGroupSettings.tsx +++ b/ts/components/settings/SessionNotificationGroupSettings.tsx @@ -43,15 +43,15 @@ export const SessionNotificationGroupSettings = (props: { hasPassword: boolean | const items = [ { - label: window.i18n('nameAndMessage'), + label: window.i18n('notificationsContentShowNameAndContent'), value: NOTIFICATION.MESSAGE, }, { - label: window.i18n('nameOnly'), + label: window.i18n('notificationsContentShowNameOnly'), value: NOTIFICATION.NAME, }, { - label: window.i18n('noNameOrMessage'), + label: window.i18n('notificationsContentShowNoNameOrContent'), value: NOTIFICATION.COUNT, }, ]; @@ -66,7 +66,7 @@ export const SessionNotificationGroupSettings = (props: { hasPassword: boolean | items.find(m => m.value === initialNotificationEnabled)?.label || window?.i18n?.('messageBody') || 'Message body', - title: window.i18n('notificationPreview'), + title: window.i18n('preview'), iconUrl: null, isExpiringMessage: false, messageSentAt: Date.now(), @@ -83,7 +83,7 @@ export const SessionNotificationGroupSettings = (props: { hasPassword: boolean | ); forceUpdate(); }} - title={window.i18n('notificationsSettingsTitle')} + title={window.i18n('sessionNotifications')} active={notificationsAreEnabled} /> {notificationsAreEnabled && isAudioNotificationSupported() && ( @@ -95,14 +95,14 @@ export const SessionNotificationGroupSettings = (props: { hasPassword: boolean | ); forceUpdate(); }} - title={window.i18n('audioNotificationsSettingsTitle')} + title={window.i18n('notificationsSoundDesktop')} active={initialAudioNotificationEnabled} /> )} {notificationsAreEnabled ? ( - + ) : null} diff --git a/ts/components/settings/SessionSettings.tsx b/ts/components/settings/SessionSettings.tsx index b9226112fb..fccbbfca1a 100644 --- a/ts/components/settings/SessionSettings.tsx +++ b/ts/components/settings/SessionSettings.tsx @@ -45,11 +45,11 @@ export enum SessionSettingCategory { Privacy = 'privacy', Notifications = 'notifications', Conversations = 'conversations', - MessageRequests = 'messageRequests', + MessageRequests = 'sessionMessageRequests', Appearance = 'appearance', Permissions = 'permissions', Help = 'help', - RecoveryPhrase = 'recoveryPhrase', + RecoveryPhrase = 'sessionRecoveryPassword', ClearData = 'ClearData', } diff --git a/ts/components/settings/SessionSettingsHeader.tsx b/ts/components/settings/SessionSettingsHeader.tsx index 2f6f357726..6101d4aefb 100644 --- a/ts/components/settings/SessionSettingsHeader.tsx +++ b/ts/components/settings/SessionSettingsHeader.tsx @@ -27,22 +27,22 @@ export const SettingsHeader = (props: Props) => { let categoryTitle: string | null = null; switch (category) { case SessionSettingCategory.Appearance: - categoryTitle = window.i18n('appearanceSettingsTitle'); + categoryTitle = window.i18n('sessionAppearance'); break; case SessionSettingCategory.Conversations: - categoryTitle = window.i18n('conversationsSettingsTitle'); + categoryTitle = window.i18n('sessionConversations'); break; case SessionSettingCategory.Notifications: - categoryTitle = window.i18n('notificationsSettingsTitle'); + categoryTitle = window.i18n('sessionNotifications'); break; case SessionSettingCategory.Help: - categoryTitle = window.i18n('helpSettingsTitle'); + categoryTitle = window.i18n('sessionHelp'); break; case SessionSettingCategory.Permissions: - categoryTitle = window.i18n('permissionsSettingsTitle'); + categoryTitle = window.i18n('sessionPermissions'); break; case SessionSettingCategory.Privacy: - categoryTitle = window.i18n('privacySettingsTitle'); + categoryTitle = window.i18n('sessionPrivacy'); break; case SessionSettingCategory.ClearData: case SessionSettingCategory.MessageRequests: diff --git a/ts/components/settings/SettingsThemeSwitcher.tsx b/ts/components/settings/SettingsThemeSwitcher.tsx index ac2ea11b22..e89ad39106 100644 --- a/ts/components/settings/SettingsThemeSwitcher.tsx +++ b/ts/components/settings/SettingsThemeSwitcher.tsx @@ -113,12 +113,14 @@ export const SettingsThemeSwitcher = () => { return ( - {window.i18n('themesSettingTitle')} + {window.i18n('appearanceThemes')} - {window.i18n('primaryColor')} + + {window.i18n('appearancePrimaryColor')} + {getPrimaryColors().map(item => { diff --git a/ts/components/settings/ZoomingSessionSlider.tsx b/ts/components/settings/ZoomingSessionSlider.tsx index cab3ab269d..ed470acf1f 100644 --- a/ts/components/settings/ZoomingSessionSlider.tsx +++ b/ts/components/settings/ZoomingSessionSlider.tsx @@ -18,7 +18,7 @@ export const ZoomingSessionSlider = (props: { onSliderChange?: (value: number) = const currentValueFromSettings = window.getSettingValue('zoom-factor-setting') || 100; return ( - +
)} @@ -43,8 +43,8 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null await ensureThemeConsistency(); } }} - title={window.i18n('matchThemeSystemSettingTitle')} - description={window.i18n('matchThemeSystemSettingDescription')} + title={window.i18n('appearanceAutoDarkMode')} + description={window.i18n('followSystemSettings')} active={isFollowSystemThemeEnabled} dataTestId="enable-follow-system-theme" /> diff --git a/ts/components/settings/section/CategoryConversations.tsx b/ts/components/settings/section/CategoryConversations.tsx index 4fd0a20741..d4e1e92ad4 100644 --- a/ts/components/settings/section/CategoryConversations.tsx +++ b/ts/components/settings/section/CategoryConversations.tsx @@ -5,6 +5,7 @@ import useUpdate from 'react-use/lib/useUpdate'; import { SettingsKey } from '../../../data/settings-key'; import { ToastUtils } from '../../../session/utils'; import { toggleAudioAutoplay } from '../../../state/ducks/userConfig'; +import { useHasEnterSendEnabled } from '../../../state/selectors/settings'; import { getAudioAutoplay } from '../../../state/selectors/userConfig'; import { SessionRadioGroup } from '../../basic/SessionRadioGroup'; import { BlockedContactsList } from '../BlockedList'; @@ -12,7 +13,6 @@ import { SessionSettingsItemWrapper, SessionToggleWithDescription, } from '../SessionSettingListItem'; -import { useHasEnterSendEnabled } from '../../../state/selectors/settings'; async function toggleCommunitiesPruning() { try { @@ -39,8 +39,8 @@ const CommunitiesPruningSetting = () => { await toggleCommunitiesPruning(); forceUpdate(); }} - title={window.i18n('pruneSettingTitle')} - description={window.i18n('pruneSettingDescription')} + title={window.i18n('conversationsMessageTrimmingTrimCommunities')} + description={window.i18n('conversationsMessageTrimmingTrimCommunitiesDescription')} active={isOpengroupPruningEnabled} /> ); @@ -59,8 +59,8 @@ const SpellCheckSetting = () => { window.toggleSpellCheck(); forceUpdate(); }} - title={window.i18n('spellCheckTitle')} - description={window.i18n('spellCheckDescription')} + title={window.i18n('conversationsSpellCheck')} + description={window.i18n('conversationsSpellCheckDescription')} active={isSpellCheckActive} /> ); @@ -77,8 +77,8 @@ const AudioMessageAutoPlaySetting = () => { dispatch(toggleAudioAutoplay()); forceUpdate(); }} - title={window.i18n('audioMessageAutoplayTitle')} - description={window.i18n('audioMessageAutoplayDescription')} + title={window.i18n('conversationsAutoplayAudioMessage')} + description={window.i18n('conversationsAutoplayAudioMessageDescription')} active={audioAutoPlay} /> ); @@ -90,19 +90,19 @@ const EnterKeyFunctionSetting = () => { const items = [ { - label: window.i18n('enterSendNewMessageDescription'), + label: window.i18n('conversationsEnterSends'), value: 'enterForSend', }, { - label: window.i18n('enterNewLineDescription'), + label: window.i18n('conversationsEnterNewLine'), value: selectedWithSettingTrue, }, ]; return ( }} buttonShape={SessionButtonShape.Square} buttonType={SessionButtonType.Solid} - buttonText={window.i18n('showDebugLog')} - title={window.i18n('reportIssue')} - description={window.i18n('shareBugDetails')} + buttonText={window.i18n('helpReportABugExportLogs')} + title={window.i18n('helpReportABug')} + description={window.i18n('helpReportABugExportLogsSaveToDesktopDescription')} /> void shell.openExternal('https://getsession.org/survey')} /> void shell.openExternal('https://crowdin.com/project/session-desktop/')} /> void shell.openExternal('https://getsession.org/faq')} /> void shell.openExternal('https://sessionapp.zendesk.com/hc/en-us')} /> diff --git a/ts/components/settings/section/CategoryPermissions.tsx b/ts/components/settings/section/CategoryPermissions.tsx index cf55b0065d..aca9f27297 100644 --- a/ts/components/settings/section/CategoryPermissions.tsx +++ b/ts/components/settings/section/CategoryPermissions.tsx @@ -14,8 +14,8 @@ const toggleCallMediaPermissions = async (triggerUIUpdate: () => void) => { if (!currentValue) { window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('callMediaPermissionsDialogTitle'), - message: window.i18n('callMediaPermissionsDialogContent'), + title: window.i18n('callsVoiceAndVideoBeta'), + message: window.i18n('callsVoiceAndVideoModalDescription'), okTheme: SessionButtonColor.Danger, okText: window.i18n('continue'), onClickOk: async () => { @@ -65,8 +65,8 @@ export const SettingsCategoryPermissions = (props: { hasPassword: boolean | null await window.toggleMediaPermissions(); forceUpdate(); }} - title={window.i18n('mediaPermissionsTitle')} - description={window.i18n('mediaPermissionsDescription')} + title={window.i18n('permissionsMicrophone')} + description={window.i18n('permissionsMicrophoneDescription')} active={Boolean(window.getSettingValue('media-permissions'))} dataTestId="enable-microphone" /> @@ -75,8 +75,8 @@ export const SettingsCategoryPermissions = (props: { hasPassword: boolean | null await toggleCallMediaPermissions(forceUpdate); forceUpdate(); }} - title={window.i18n('callMediaPermissionsTitle')} - description={window.i18n('callMediaPermissionsDescription')} + title={window.i18n('callsVoiceAndVideoBeta')} + description={window.i18n('callsVoiceAndVideoToggleDescription')} active={Boolean(window.getCallMediaPermissions())} dataTestId="enable-calls" /> @@ -86,8 +86,8 @@ export const SettingsCategoryPermissions = (props: { hasPassword: boolean | null await window.setSettingValue(SettingsKey.settingsAutoUpdate, !old); forceUpdate(); }} - title={window.i18n('autoUpdateSettingTitle')} - description={window.i18n('autoUpdateSettingDescription')} + title={window.i18n('permissionsAutoUpdate')} + description={window.i18n('permissionsAutoUpdateDescription')} active={Boolean(window.getSettingValue(SettingsKey.settingsAutoUpdate))} /> diff --git a/ts/components/settings/section/CategoryPrivacy.tsx b/ts/components/settings/section/CategoryPrivacy.tsx index 59e836d524..13695d68c0 100644 --- a/ts/components/settings/section/CategoryPrivacy.tsx +++ b/ts/components/settings/section/CategoryPrivacy.tsx @@ -24,8 +24,8 @@ async function toggleLinkPreviews(isToggleOn: boolean, forceUpdate: () => void) if (!isToggleOn) { window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('linkPreviewsTitle'), - message: window.i18n('linkPreviewsConfirmMessage'), + title: window.i18n('linkPreviewsSend'), + message: window.i18n('linkPreviewsSendModalDescription'), okTheme: SessionButtonColor.Danger, onClickOk: async () => { const newValue = !isToggleOn; @@ -70,8 +70,8 @@ export const SettingsCategoryPrivacy = (props: { await window.setSettingValue(SettingsKey.settingsReadReceipt, !old); forceUpdate(); }} - title={window.i18n('readReceiptSettingTitle')} - description={window.i18n('readReceiptSettingDescription')} + title={window.i18n('readReceipts')} + description={window.i18n('readReceiptsDescription')} active={window.getSettingValue(SettingsKey.settingsReadReceipt)} dataTestId="enable-read-receipts" /> @@ -81,8 +81,8 @@ export const SettingsCategoryPrivacy = (props: { await window.setSettingValue(SettingsKey.settingsTypingIndicator, !old); forceUpdate(); }} - title={window.i18n('typingIndicatorsSettingTitle')} - description={window.i18n('typingIndicatorsSettingDescription')} + title={window.i18n('typingIndicators')} + description={window.i18n('typingIndicatorsDescription')} active={Boolean(window.getSettingValue(SettingsKey.settingsTypingIndicator))} childrenDescription={} /> @@ -90,8 +90,8 @@ export const SettingsCategoryPrivacy = (props: { onClickToggle={() => { void toggleLinkPreviews(isLinkPreviewsOn, forceUpdate); }} - title={window.i18n('linkPreviewsTitle')} - description={window.i18n('linkPreviewDescription')} + title={window.i18n('linkPreviewsSend')} + description={window.i18n('linkPreviewsDescription')} active={isLinkPreviewsOn} /> {!props.hasPassword && ( { displayPasswordModal('set', props.onPasswordUpdated); }} - buttonText={window.i18n('setPassword')} + buttonText={window.i18n('passwordSet')} dataTestId={'set-password-button'} /> )} {props.hasPassword && ( { displayPasswordModal('change', props.onPasswordUpdated); }} - buttonText={window.i18n('changePassword')} + buttonText={window.i18n('passwordChange')} dataTestId="change-password-settings-button" /> )} {props.hasPassword && ( { displayPasswordModal('remove', props.onPasswordUpdated); }} buttonColor={SessionButtonColor.Danger} - buttonText={window.i18n('removePassword')} + buttonText={window.i18n('passwordRemove')} dataTestId="remove-password-settings-button" /> )} diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index 662f1199e2..7f603ca823 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -68,7 +68,7 @@ export function useConversationsUsernameWithQuoteOrFullPubkey(pubkeys: Array { return pubkeys.map(pubkey => { if (pubkey === UserUtils.getOurPubKeyStrFromCache() || pubkey.toLowerCase() === 'you') { - return window.i18n('you'); + return window.i18n('onionRoutingPathYou'); } const convo = state.conversations.conversationLookup[pubkey]; const nameGot = convo?.displayNameInProfile; @@ -359,7 +359,7 @@ export function useQuoteAuthorName( const isMe = Boolean(authorId && isUsAnySogsFromCache(authorId)); const authorName = isMe - ? window.i18n('you') + ? window.i18n('onionRoutingPathYou') : convoProps?.nickname || convoProps?.isPrivate ? convoProps?.displayNameInProfile : undefined; @@ -401,13 +401,13 @@ export function useDisappearingMessageSettingText({ // TODO legacy messages support will be removed in a future release const expirationModeText = expirationMode === 'deleteAfterRead' - ? window.i18n('disappearingMessagesModeAfterRead') + ? window.i18n('disappearingMessagesDisappearAfterRead') : expirationMode === 'deleteAfterSend' - ? window.i18n('disappearingMessagesModeAfterSend') + ? window.i18n('disappearingMessagesDisappearAfterSend') : expirationMode === 'legacy' ? isMe || (isGroup && !isPublic) - ? window.i18n('disappearingMessagesModeAfterSend') - : window.i18n('disappearingMessagesModeAfterRead') + ? window.i18n('disappearingMessagesDisappearAfterSend') + : window.i18n('disappearingMessagesDisappearAfterRead') : null; const expireTimerText = isNumber(expireTimer) diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index 98d18929d0..033f513884 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -91,7 +91,10 @@ export async function blockConvoById(conversationId: string) { await BlockedNumberController.block(conversation.id); await conversation.commit(); - ToastUtils.pushToastSuccess('blocked', window.i18n('blocked')); + ToastUtils.pushToastSuccess( + 'blocked', + window.i18n('blocked', { name: conversation.getNicknameOrRealUsernameOrPlaceholder() ?? '' }) + ); } export async function unblockConvoById(conversationId: string) { @@ -101,14 +104,24 @@ export async function unblockConvoById(conversationId: string) { // we assume it's a block contact and not group. // this is to be able to unlock a contact we don't have a conversation with. await BlockedNumberController.unblockAll([conversationId]); - ToastUtils.pushToastSuccess('unblocked', window.i18n('unblocked')); + ToastUtils.pushToastSuccess( + 'unblocked', + window.i18n('blockUnblockedUser', { + name: conversationId, + }) + ); return; } if (!conversation.id || conversation.isPublic()) { return; } await BlockedNumberController.unblockAll([conversationId]); - ToastUtils.pushToastSuccess('unblocked', window.i18n('unblocked')); + ToastUtils.pushToastSuccess( + 'unblocked', + window.i18n('blockUnblockedUser', { + name: conversation.getNicknameOrRealUsernameOrPlaceholder() ?? '', + }) + ); await conversation.commit(); } @@ -187,7 +200,7 @@ export const declineConversationWithConfirm = ({ updateConfirmModal({ okText: blockContact ? window.i18n('block') : window.i18n('decline'), cancelText: window.i18n('cancel'), - message: window.i18n('declineRequestMessage'), + message: window.i18n('messageRequestsDelete'), onClickOk: async () => { await declineConversationWithoutConfirm({ conversationId, @@ -232,10 +245,7 @@ export async function showUpdateGroupMembersByConvoId(conversationId: string) { window.inboxStore?.dispatch(updateGroupMembersModal({ conversationId })); } -export function showLeavePrivateConversationbyConvoId( - conversationId: string, - name: string | undefined -) { +export function showLeavePrivateConversationbyConvoId(conversationId: string) { const conversation = getConversationController().get(conversationId); const isMe = conversation.isMe(); @@ -274,10 +284,10 @@ export function showLeavePrivateConversationbyConvoId( window?.inboxStore?.dispatch( updateConfirmModal({ - title: isMe ? window.i18n('hideConversation') : window.i18n('deleteConversation'), + title: isMe ? window.i18n('noteToSelfHide') : window.i18n('conversationsDelete'), message: isMe - ? window.i18n('hideNoteToSelfConfirmation') - : window.i18n('deleteConversationConfirmation', name ? [name] : ['']), + ? window.i18n('noteToSelfHideDescription') + : window.i18n('deleteMessagesDescriptionEveryone'), onClickOk, okText: isMe ? window.i18n('hide') : window.i18n('delete'), okTheme: SessionButtonColor.Danger, @@ -364,8 +374,8 @@ export async function showLeaveGroupByConvoId(conversationId: string, name: stri // NOTE For legacy closed groups window?.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('leaveGroup'), - message: window.i18n('leaveGroupConrirmationOnlyAdminLegacy', name ? [name] : ['']), + title: window.i18n('groupLeave'), + message: window.i18n('groupLeaveDescriptionAdmin', { groupname: name ?? '' }), onClickOk, okText: window.i18n('leave'), okTheme: SessionButtonColor.Danger, @@ -382,8 +392,8 @@ export async function showLeaveGroupByConvoId(conversationId: string, name: stri // }; // window?.inboxStore?.dispatch( // updateConfirmModal({ - // title: window.i18n('leaveGroup'), - // message: window.i18n('leaveGroupConfirmationOnlyAdmin', name ? [name] : ['']), + // title: window.i18n('groupLeave'), + // message: window.i18n('leaveGroupConfirmationOnlyAdmin', {name: name ?? ''}), // messageSub: window.i18n('leaveGroupConfirmationOnlyAdminWarning'), // onClickOk: onClickOkLastAdmin, // okText: window.i18n('addModerator'), @@ -399,8 +409,8 @@ export async function showLeaveGroupByConvoId(conversationId: string, name: stri } else if (isPublic || (isClosedGroup && !isAdmin)) { window?.inboxStore?.dispatch( updateConfirmModal({ - title: isPublic ? window.i18n('leaveCommunity') : window.i18n('leaveGroup'), - message: window.i18n('leaveGroupConfirmation', name ? [name] : ['']), + title: isPublic ? window.i18n('communityLeave') : window.i18n('groupLeave'), + message: window.i18n('groupLeaveDescription', { groupname: name ?? '' }), onClickOk, okText: window.i18n('leave'), okTheme: SessionButtonColor.Danger, @@ -718,8 +728,8 @@ export async function showLinkSharingConfirmationModalDialog(e: any) { updateConfirmModal({ shouldShowConfirm: !window.getSettingValue(SettingsKey.settingsLinkPreview) && !alreadyDisplayedPopup, - title: window.i18n('linkPreviewsTitle'), - message: window.i18n('linkPreviewsConfirmMessage'), + title: window.i18n('linkPreviewsSend'), + message: window.i18n('linkPreviewsSendModalDescription'), okTheme: SessionButtonColor.Danger, onClickOk: async () => { await window.setSettingValue(SettingsKey.settingsLinkPreview, true); diff --git a/ts/interactions/conversations/unsendingInteractions.ts b/ts/interactions/conversations/unsendingInteractions.ts index bb4b6e0435..3021fc3de5 100644 --- a/ts/interactions/conversations/unsendingInteractions.ts +++ b/ts/interactions/conversations/unsendingInteractions.ts @@ -68,7 +68,7 @@ async function unsendMessagesForEveryone( await deleteMessagesFromSwarmAndCompletelyLocally(conversation, msgsToDelete); window.inboxStore?.dispatch(resetSelectedMessageIds()); - ToastUtils.pushDeleted(msgsToDelete.length); + ToastUtils.pushDeleted(); } function getUnsendMessagesObjects(messages: Array) { @@ -235,7 +235,7 @@ async function unsendMessageJustForThisUser( // Update view and trigger update window.inboxStore?.dispatch(resetSelectedMessageIds()); - ToastUtils.pushDeleted(msgsToDelete.length); + ToastUtils.pushDeleted(); } const doDeleteSelectedMessagesInSOGS = async ( @@ -277,7 +277,7 @@ const doDeleteSelectedMessagesInSOGS = async ( }) ); // successful deletion - ToastUtils.pushDeleted(toDeleteLocallyIds.length); + ToastUtils.pushDeleted(); window.inboxStore?.dispatch(resetSelectedMessageIds()); // #endregion }; @@ -326,7 +326,7 @@ const doDeleteSelectedMessages = async ({ // Update view and trigger update window.inboxStore?.dispatch(resetSelectedMessageIds()); - ToastUtils.pushDeleted(selectedMessages.length); + ToastUtils.pushDeleted(); return; } // otherwise, delete that message locally, from our swarm and from our other devices @@ -351,11 +351,9 @@ export async function deleteMessagesByIdForEveryone( window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('deleteForEveryone'), - message: moreThanOne - ? window.i18n('deleteMessagesQuestion', [messageCount.toString()]) - : window.i18n('deleteMessageQuestion'), - okText: window.i18n('deleteForEveryone'), + title: window.i18n('clearMessagesForEveryone'), + message: moreThanOne ? window.i18n('deleteMessages') : window.i18n('deleteMessage'), + okText: window.i18n('clearMessagesForEveryone'), okTheme: SessionButtonColor.Danger, onClickOk: async () => { await doDeleteSelectedMessages({ selectedMessages, conversation, deleteForEveryone: true }); @@ -378,20 +376,17 @@ export async function deleteMessagesById(messageIds: Array, conversation const isMe = conversation.isMe(); - const messageCount = selectedMessages.length; const moreThanOne = selectedMessages.length > 1; const closeDialog = () => window.inboxStore?.dispatch(updateConfirmModal(null)); window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('deleteJustForMe'), - message: moreThanOne - ? window.i18n('deleteMessagesQuestion', [messageCount.toString()]) - : window.i18n('deleteMessageQuestion'), + title: window.i18n('clearMessagesForMe'), + message: moreThanOne ? window.i18n('deleteMessages') : window.i18n('deleteMessage'), radioOptions: !isMe ? [ - { label: window.i18n('deleteJustForMe'), value: 'deleteJustForMe' }, - { label: window.i18n('deleteForEveryone'), value: 'deleteForEveryone' }, + { label: window.i18n('clearMessagesForMe'), value: 'clearMessagesForMe' }, + { label: window.i18n('clearMessagesForEveryone'), value: 'clearMessagesForEveryone' }, ] : undefined, okText: window.i18n('delete'), diff --git a/ts/interactions/messageInteractions.ts b/ts/interactions/messageInteractions.ts index 2e4d15eea5..7a89db01e9 100644 --- a/ts/interactions/messageInteractions.ts +++ b/ts/interactions/messageInteractions.ts @@ -111,12 +111,10 @@ const acceptOpenGroupInvitationV2 = (completeUrl: string, roomName?: string) => window.inboxStore?.dispatch( updateConfirmModal({ - title: window.i18n('joinOpenGroupAfterInvitationConfirmationTitle', [ - roomName || window.i18n('unknown'), - ]), - message: window.i18n('joinOpenGroupAfterInvitationConfirmationDesc', [ - roomName || window.i18n('unknown'), - ]), + title: window.i18n('communityJoin'), + message: window.i18n('communityJoinDescription', { + communityname: roomName ?? window.i18n('unknown'), + }), onClickOk: async () => { await joinOpenGroupV2WithUIEvents(completeUrl, true, false); }, diff --git a/ts/mains/main_node.ts b/ts/mains/main_node.ts index 8070467389..0ae534bfa3 100644 --- a/ts/mains/main_node.ts +++ b/ts/mains/main_node.ts @@ -157,13 +157,13 @@ if (windowFromUserConfig) { // import {load as loadLocale} from '../..' import { getAppRootPath } from '../node/getRootPath'; import { setLastestRelease } from '../node/latest_desktop_release'; -import { load as loadLocale, LocaleMessagesWithNameType } from '../node/locale'; +import { load as loadLocale } from '../node/locale'; import { isDevProd, isTestIntegration } from '../shared/env_vars'; import { classicDark } from '../themes'; // Both of these will be set after app fires the 'ready' event let logger: Logger | null = null; -let locale: LocaleMessagesWithNameType; +let locale: ReturnType; function assertLogger(): Logger { if (!logger) { @@ -862,10 +862,13 @@ async function requestShutdown() { // exits the app before we've set everything up in preload() (so the browser isn't // yet listening for these events), or if there are a whole lot of stacked-up tasks. // Note: two minutes is also our timeout for SQL tasks in data.ts in the browser. - setTimeout(() => { - console.log('requestShutdown: Response never received; forcing shutdown.'); - resolve(undefined); - }, 2 * 60 * 1000); + setTimeout( + () => { + console.log('requestShutdown: Response never received; forcing shutdown.'); + resolve(undefined); + }, + 2 * 60 * 1000 + ); }); try { @@ -974,7 +977,7 @@ ipc.on('password-window-login', async (event, passPhrase) => { await showMainWindow(passPhrase, passwordAttempt); sendResponse(undefined); } catch (e) { - const localisedError = locale.messages.removePasswordInvalid; + const localisedError = locale.messages.passwordIncorrect; sendResponse(localisedError); } }); @@ -1036,7 +1039,7 @@ ipc.on('set-password', async (event, passPhrase, oldPhrase) => { const hashMatches = oldPhrase && PasswordUtil.matchesHash(oldPhrase, hash); if (hash && !hashMatches) { - const incorrectOldPassword = locale.messages.invalidOldPassword; + const incorrectOldPassword = locale.messages.passwordCurrentIncorrect; sendResponse( incorrectOldPassword || 'Failed to set password: Old password provided is invalid' ); @@ -1057,7 +1060,7 @@ ipc.on('set-password', async (event, passPhrase, oldPhrase) => { sendResponse(undefined); } catch (e) { - const localisedError = locale.messages.setPasswordFail; + const localisedError = locale.messages.passwordFailed; sendResponse(localisedError || 'Failed to set password'); } }); diff --git a/ts/models/message.ts b/ts/models/message.ts index fbf0c35ad3..ea60b1d123 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -277,7 +277,10 @@ export class MessageModel extends Backbone.Model { pubkey ); if (isUS) { - description = description?.replace(pubkeyWithAt, `@${window.i18n('you')}`); + description = description?.replace( + pubkeyWithAt, + `@${window.i18n('onionRoutingPathYou')}` + ); } else if (displayName && displayName.length) { description = description?.replace(pubkeyWithAt, `@${displayName}`); } @@ -285,7 +288,7 @@ export class MessageModel extends Backbone.Model { return description; } if ((this.get('attachments') || []).length > 0) { - return window.i18n('mediaMessage'); + return window.i18n('contentDescriptionMediaMessage'); } if (this.isExpirationTimerUpdate()) { const expireTimerUpdate = this.getExpirationTimerUpdate(); @@ -301,13 +304,20 @@ export class MessageModel extends Backbone.Model { expireTimer ); + const source = expireTimerUpdate?.source; + if (!expireTimerUpdate || expirationMode === 'off' || !expireTimer || expireTimer === 0) { - return window.i18n('disappearingMessagesDisabled'); + return window.i18n('disappearingMessagesTurnedOff', { + name: source + ? getConversationController().get(source)?.getNicknameOrRealUsernameOrPlaceholder() ?? + '' + : '', + }); } - return window.i18n('timerSetTo', [ - TimerOptions.getAbbreviated(expireTimerUpdate.expireTimer || 0), - ]); + return window.i18n('timerSetTo', { + time: TimerOptions.getAbbreviated(expireTimerUpdate.expireTimer || 0), + }); } return ''; @@ -814,7 +824,7 @@ export class MessageModel extends Backbone.Model { public async markAsDeleted() { this.set({ isDeleted: true, - body: window.i18n('messageDeletedPlaceholder'), + body: window.i18n('deleteMessageDeleted'), quote: undefined, groupInvitation: undefined, dataExtractionNotification: undefined, @@ -1276,23 +1286,25 @@ export class MessageModel extends Backbone.Model { } if (arrayContainsUsOnly(groupUpdate.left)) { - return window.i18n('youLeftTheGroup'); + return window.i18n('groupMemberYouLeft'); } if (groupUpdate.left && groupUpdate.left.length === 1) { - return window.i18n('leftTheGroup', [ - getConversationController().getContactProfileNameOrShortenedPubKey(groupUpdate.left[0]), - ]); + return window.i18n('groupMemberLeft', { + name: getConversationController().getContactProfileNameOrShortenedPubKey( + groupUpdate.left[0] + ), + }); } const messages = []; if (!groupUpdate.name && !groupUpdate.joined && !groupUpdate.kicked && !groupUpdate.kicked) { - return window.i18n('updatedTheGroup'); // Group Updated + return window.i18n('groupUpdated'); } if (groupUpdate.name) { - return window.i18n('titleIsNow', [groupUpdate.name]); + return window.i18n('groupNameNew', { groupname: groupUpdate.name }); } if (groupUpdate.joined && groupUpdate.joined.length) { @@ -1300,11 +1312,8 @@ export class MessageModel extends Backbone.Model { getConversationController().getContactProfileNameOrShortenedPubKey ); - if (names.length > 1) { - messages.push(window.i18n('multipleJoinedTheGroup', [names.join(', ')])); - } else { - messages.push(window.i18n('joinedTheGroup', names)); - } + messages.push(window.i18n('groupMemberNew', { name: names.join(', ') })); + return messages.join(' '); } @@ -1315,20 +1324,16 @@ export class MessageModel extends Backbone.Model { ); if (names.length > 1) { - messages.push(window.i18n('multipleKickedFromTheGroup', [names.join(', ')])); + messages.push(window.i18n('multipleKickedFromTheGroup', { name: names.join(', ') })); } else { - messages.push(window.i18n('kickedFromTheGroup', names)); + messages.push(window.i18n('groupRemoved', { name: names[0] })); } } return messages.join(' '); } - if (this.isIncoming() && this.hasErrors()) { - return window.i18n('incomingError'); - } - if (this.isGroupInvitation()) { - return `😎 ${window.i18n('openGroupInvitation')}`; + return `😎 ${window.i18n('communityInvitation')}`; } if (this.isDataExtractionNotification()) { @@ -1336,28 +1341,32 @@ export class MessageModel extends Backbone.Model { 'dataExtractionNotification' ) as DataExtractionNotificationMsg; if (dataExtraction.type === SignalService.DataExtractionNotification.Type.SCREENSHOT) { - return window.i18n('tookAScreenshot', [ - getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source), - ]); + return window.i18n('screenshotTaken', { + name: getConversationController().getContactProfileNameOrShortenedPubKey( + dataExtraction.source + ), + }); } - return window.i18n('savedTheFile', [ - getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source), - ]); + return window.i18n('attachmentsMediaSaved', { + name: getConversationController().getContactProfileNameOrShortenedPubKey( + dataExtraction.source + ), + }); } if (this.isCallNotification()) { - const displayName = getConversationController().getContactProfileNameOrShortenedPubKey( + const name = getConversationController().getContactProfileNameOrShortenedPubKey( this.get('conversationId') ); const callNotificationType = this.get('callNotificationType'); if (callNotificationType === 'missed-call') { - return window.i18n('callMissed', [displayName]); + return window.i18n('callsMissedCallFrom', { name }); } if (callNotificationType === 'started-call') { - return window.i18n('startedACall', [displayName]); + return window.i18n('callsYouCalled', { name }); } if (callNotificationType === 'answered-a-call') { - return window.i18n('answeredACall', [displayName]); + return window.i18n('answeredACall', { name }); } } @@ -1381,8 +1390,8 @@ export class MessageModel extends Backbone.Model { return isCommunity ? window.i18n('leaveCommunityFailed') : isGroup - ? window.i18n('leaveGroupFailed') - : window.i18n('deleteConversationFailed'); + ? window.i18n('groupErrorLeave') + : window.i18n('deleteConversationFailed'); default: assertUnreachable( interactionType, @@ -1396,7 +1405,7 @@ export class MessageModel extends Backbone.Model { if (this.get('reaction')) { const reaction = this.get('reaction'); if (reaction && reaction.emoji && reaction.emoji !== '') { - return window.i18n('reactionNotification', [reaction.emoji]); + return window.i18n('emojiReactsNotification', { emoji: reaction.emoji }); } } return this.get('body'); @@ -1461,7 +1470,7 @@ export function findAndFormatContact(pubkey: string): FindAndFormatContactType { pubkey === UserUtils.getOurPubKeyStrFromCache() || (pubkey && PubKey.isBlinded(pubkey) && isUsAnySogsFromCache(pubkey)) ) { - profileName = window.i18n('you'); + profileName = window.i18n('onionRoutingPathYou'); isMe = true; } else { profileName = contactModel?.getNicknameOrRealUsername() || null; diff --git a/ts/node/menu.ts b/ts/node/menu.ts index a9516a4564..6fcc4e4c04 100644 --- a/ts/node/menu.ts +++ b/ts/node/menu.ts @@ -1,5 +1,5 @@ import { isString } from 'lodash'; -import { LocaleMessagesType } from './locale'; +import type { LocalizerDictionary } from '../types/Localizer'; import { Noop } from '../types/Util'; /** @@ -9,9 +9,9 @@ import { Noop } from '../types/Util'; * @param label - The label for the menu item * @returns The label with the accelerator prefix */ -const withAcceleratorPrefix = (label:string) => { +const withAcceleratorPrefix = (label: string) => { return `&${label}`; -} +}; export const createTemplate = ( options: { @@ -22,20 +22,14 @@ export const createTemplate = ( showDebugLog: () => void; showWindow: () => void; }, - messages: LocaleMessagesType + messages: LocalizerDictionary ) => { if (!isString(options.platform)) { throw new TypeError('`options.platform` must be a string'); } - const { - openReleaseNotes, - openSupportPage, - platform, - showAbout, - showDebugLog, - showWindow, - } = options; + const { openReleaseNotes, openSupportPage, platform, showAbout, showDebugLog, showWindow } = + options; const template = [ { @@ -46,7 +40,7 @@ export const createTemplate = ( }, { role: 'quit', - label: messages.appMenuQuit, + label: messages.quit, }, ], }, @@ -55,26 +49,26 @@ export const createTemplate = ( submenu: [ { role: 'undo', - label: messages.editMenuUndo, + label: messages.undo, }, { role: 'redo', - label: messages.editMenuRedo, + label: messages.redo, }, { type: 'separator', }, { role: 'cut', - label: messages.editMenuCut, + label: messages.cut, }, { role: 'copy', - label: messages.editMenuCopy, + label: messages.copy, }, { role: 'paste', - label: messages.editMenuPaste, + label: messages.paste, }, { role: 'delete', @@ -82,7 +76,7 @@ export const createTemplate = ( }, { role: 'selectall', - label: messages.editMenuSelectAll, + label: messages.selectAll, }, ], }, @@ -91,23 +85,23 @@ export const createTemplate = ( submenu: [ { role: 'resetzoom', - label: messages.viewMenuResetZoom, + label: messages.actualSize, }, { accelerator: platform === 'darwin' ? 'Command+=' : 'Control+Plus', role: 'zoomin', - label: messages.viewMenuZoomIn, + label: messages.appearanceZoomIn, }, { role: 'zoomout', - label: messages.viewMenuZoomOut, + label: messages.appearanceZoomOut, }, { type: 'separator', }, { role: 'togglefullscreen', - label: messages.viewMenuToggleFullScreen, + label: messages.fullScreenToggle, }, { type: 'separator', @@ -121,7 +115,7 @@ export const createTemplate = ( }, { role: 'toggledevtools', - label: messages.viewMenuToggleDevTools, + label: messages.developerToolsToggle, }, ], }, @@ -131,7 +125,7 @@ export const createTemplate = ( submenu: [ { role: 'minimize', - label: messages.windowMenuMinimize, + label: messages.minimize, }, ], }, @@ -140,14 +134,14 @@ export const createTemplate = ( role: 'help', submenu: [ { - label: messages.goToReleaseNotes, + label: messages.updateReleaseNotes, click: openReleaseNotes, }, { type: 'separator', }, { - label: messages.goToSupportPage, + label: messages.supportGoTo, click: openSupportPage, }, { @@ -173,7 +167,7 @@ export const createTemplate = ( function updateForMac( template: any, - messages: LocaleMessagesType, + messages: LocalizerDictionary, options: { showAbout: Noop; showWindow: Noop } ) { const { showAbout, showWindow } = options; @@ -200,22 +194,22 @@ function updateForMac( type: 'separator', }, { - label: messages.appMenuHide, + label: messages.hide, role: 'hide', }, { - label: messages.appMenuHideOthers, + label: messages.hideOthers, role: 'hideothers', }, { - label: messages.appMenuUnhide, + label: messages.showAll, role: 'unhide', }, { type: 'separator', }, { - label: messages.appMenuQuit, + label: messages.quit, role: 'quit', }, ], @@ -226,17 +220,17 @@ function updateForMac( // eslint-disable-next-line no-param-reassign template[windowMenuTemplateIndex].submenu = [ { - label: messages.windowMenuClose, + label: messages.closeWindow, accelerator: 'CmdOrCtrl+W', role: 'close', }, { - label: messages.windowMenuMinimize, + label: messages.minimize, accelerator: 'CmdOrCtrl+M', role: 'minimize', }, { - label: messages.windowMenuZoom, + label: messages.appearanceZoom, role: 'zoom', }, { diff --git a/ts/node/spell_check.ts b/ts/node/spell_check.ts index 93eceab72c..6368213e7b 100644 --- a/ts/node/spell_check.ts +++ b/ts/node/spell_check.ts @@ -36,7 +36,7 @@ export const setup = (browserWindow: BrowserWindow, messages: any) => { ); } else { template.push({ - label: messages.contextMenuNoSuggestions, + label: messages.noSuggestions, enabled: false, }); } @@ -45,30 +45,30 @@ export const setup = (browserWindow: BrowserWindow, messages: any) => { if (params.isEditable) { if (editFlags.canUndo) { - template.push({ label: messages.editMenuUndo, role: 'undo' }); + template.push({ label: messages.undo, role: 'undo' }); } // This is only ever `true` if undo was triggered via the context menu // (not ctrl/cmd+z) if (editFlags.canRedo) { - template.push({ label: messages.editMenuRedo, role: 'redo' }); + template.push({ label: messages.redo, role: 'redo' }); } if (editFlags.canUndo || editFlags.canRedo) { template.push({ type: 'separator' }); } if (editFlags.canCut) { - template.push({ label: messages.editMenuCut, role: 'cut' }); + template.push({ label: messages.cut, role: 'cut' }); } } if (editFlags.canPaste) { - template.push({ label: messages.editMenuPaste, role: 'paste' }); + template.push({ label: messages.paste, role: 'paste' }); } // Only enable select all in editors because select all in non-editors // results in all the UI being selected if (editFlags.canSelectAll && params.isEditable) { template.push({ - label: messages.editMenuSelectAll, + label: messages.selectAll, role: 'selectall', }); } diff --git a/ts/node/sql.ts b/ts/node/sql.ts index 52a12d6e05..029d4592f1 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -46,7 +46,7 @@ import { OPEN_GROUP_ROOMS_V2_TABLE, toSqliteBoolean, } from './database_utility'; -import { LocaleMessagesType } from './locale'; // checked - only node +import type { LocalizerDictionary } from '../types/Localizer'; // checked - only node import { StorageItem } from './storage_item'; // checked - only node import { OpenGroupV2Room } from '../data/opengroups'; @@ -142,7 +142,7 @@ async function initializeSql({ }: { configDir: string; key: string; - messages: LocaleMessagesType; + messages: LocalizerDictionary; passwordAttempt: boolean; }) { console.info('initializeSql sqlnode'); @@ -207,10 +207,10 @@ async function initializeSql({ } console.log('Database startup error:', error.stack); const button = await dialog.showMessageBox({ - buttons: [messages.copyErrorAndQuit, messages.clearAllData], + buttons: [messages.errorCopyAndQuit, messages.clearDataAll], defaultId: 0, detail: redactAll(error.stack), - message: messages.databaseError, + message: messages.errorDatabase, noLink: true, type: 'error', }); diff --git a/ts/node/tray_icon.ts b/ts/node/tray_icon.ts index 65d158ca2d..f9e2ad1c7b 100644 --- a/ts/node/tray_icon.ts +++ b/ts/node/tray_icon.ts @@ -1,7 +1,7 @@ import path from 'path'; import { app, BrowserWindow, Menu, Tray } from 'electron'; -import { LocaleMessagesType } from './locale'; +import { LocalizerDictionary } from '../types/Localizer'; import { getAppRootPath } from './getRootPath'; let trayContextMenu = null; @@ -10,7 +10,7 @@ let trayAny: any; export function createTrayIcon( getMainWindow: () => BrowserWindow | null, - messages: LocaleMessagesType + messages: LocalizerDictionary ) { // keep the duplicated part to allow for search and find const iconFile = process.platform === 'darwin' ? 'session_icon_16.png' : 'session_icon_32.png'; @@ -64,12 +64,12 @@ export function createTrayIcon( trayContextMenu = Menu.buildFromTemplate([ { id: 'toggleWindowVisibility', - label: messages[mainWindow?.isVisible() ? 'appMenuHide' : 'show'], + label: messages[mainWindow?.isVisible() ? 'hide' : 'show'], click: trayAny.toggleWindowVisibility, }, { id: 'quit', - label: messages.appMenuQuit, + label: messages.quit, click: app.quit.bind(app), }, ]); diff --git a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts index 02457ad4f0..1eb313faca 100644 --- a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts +++ b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts @@ -95,7 +95,7 @@ async function joinOpenGroupV2( if (!conversation) { window?.log?.warn('Failed to join open group v2'); - throw new Error(window.i18n('connectToServerFail')); + throw new Error(window.i18n('groupErrorJoin')); } // here we managed to connect to the group. @@ -151,12 +151,12 @@ export async function joinOpenGroupV2WithUIEvents( await existingConvo.setIsApproved(true, false); await existingConvo.commit(); if (showToasts) { - ToastUtils.pushToastError('publicChatExists', window.i18n('publicChatExists')); + ToastUtils.pushToastError('publicChatExists', window.i18n('communityJoinedAlready')); } return false; } if (showToasts) { - ToastUtils.pushToastInfo('connectingToServer', window.i18n('connectingToServer')); + ToastUtils.pushToastInfo('connectingToServer', window.i18n('callsConnecting')); } uiCallback?.({ loadingState: 'started', conversationKey: conversationID }); @@ -165,24 +165,21 @@ export async function joinOpenGroupV2WithUIEvents( if (convoCreated) { if (showToasts) { - ToastUtils.pushToastSuccess( - 'connectToServerSuccess', - window.i18n('connectToServerSuccess') - ); + ToastUtils.pushToastSuccess('connectToServerSuccess', window.i18n('communityJoined')); } uiCallback?.({ loadingState: 'finished', conversationKey: convoCreated?.id }); return true; } if (showToasts) { - ToastUtils.pushToastError('connectToServerFail', window.i18n('connectToServerFail')); + ToastUtils.pushToastError('connectToServerFail', window.i18n('groupErrorJoin')); } uiCallback?.({ loadingState: 'failed', conversationKey: conversationID }); } catch (error) { window?.log?.warn('got error while joining open group:', error.message); if (showToasts) { - ToastUtils.pushToastError('connectToServerFail', window.i18n('connectToServerFail')); + ToastUtils.pushToastError('connectToServerFail', window.i18n('groupErrorJoin')); } uiCallback?.({ loadingState: 'failed', conversationKey: null }); } diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts index 2651a36323..1eb7fe982f 100644 --- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts +++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts @@ -157,7 +157,7 @@ export class OpenGroupManagerV2 { if (getConversationController().get(conversationId)) { // Url incorrect or server not compatible - throw new Error(window.i18n('publicChatExists')); + throw new Error(window.i18n('communityJoinedAlready')); } try { diff --git a/ts/session/conversations/createClosedGroup.ts b/ts/session/conversations/createClosedGroup.ts index e30afa86ab..4de4acfb3a 100644 --- a/ts/session/conversations/createClosedGroup.ts +++ b/ts/session/conversations/createClosedGroup.ts @@ -117,15 +117,10 @@ async function sendToGroupMembers( if (allInvitesSent) { if (isRetry) { - const invitesTitle = - inviteResults.length > 1 - ? window.i18n('closedGroupInviteSuccessTitlePlural') - : window.i18n('closedGroupInviteSuccessTitle'); - window.inboxStore?.dispatch( updateConfirmModal({ - title: invitesTitle, - message: window.i18n('closedGroupInviteSuccessMessage'), + title: window.i18n('groupInviteSuccessful'), + message: window.i18n('groupInviteSuccessful'), hideCancel: true, onClickClose: () => { window.inboxStore?.dispatch(updateConfirmModal(null)); @@ -147,7 +142,7 @@ async function sendToGroupMembers( inviteResults.length > 1 ? window.i18n('closedGroupInviteFailMessagePlural') : window.i18n('closedGroupInviteFailMessage'), - okText: window.i18n('closedGroupInviteOkText'), + okText: window.i18n('resend'), onClickOk: async () => { const membersToResend: Array = new Array(); inviteResults.forEach((result, index) => { diff --git a/ts/session/disappearing_messages/timerOptions.ts b/ts/session/disappearing_messages/timerOptions.ts index 1dc1267f31..bd9c51bbd8 100644 --- a/ts/session/disappearing_messages/timerOptions.ts +++ b/ts/session/disappearing_messages/timerOptions.ts @@ -1,6 +1,5 @@ import moment from 'moment'; import { isDevProd } from '../../shared/env_vars'; -import { LocalizerKeys } from '../../types/LocalizerKeys'; type TimerOptionsEntry = { name: string; value: number }; export type TimerOptionsArray = Array; @@ -32,15 +31,44 @@ const timerOptionsDurations: Array<{ }; }); +// TODO - This is copied from the messages.json file as a temporary solution. This will be replaced once time localization is completed. +type TimerOptionKey = + | 'timerOption_0_seconds' + | 'timerOption_0_seconds_abbreviated' + | 'timerOption_10_seconds' + | 'timerOption_10_seconds_abbreviated' + | 'timerOption_12_hours' + | 'timerOption_12_hours_abbreviated' + | 'timerOption_1_day' + | 'timerOption_1_day_abbreviated' + | 'timerOption_1_hour' + | 'timerOption_1_hour_abbreviated' + | 'timerOption_1_minute' + | 'timerOption_1_minute_abbreviated' + | 'timerOption_1_week' + | 'timerOption_1_week_abbreviated' + | 'timerOption_2_weeks' + | 'timerOption_2_weeks_abbreviated' + | 'timerOption_30_minutes' + | 'timerOption_30_minutes_abbreviated' + | 'timerOption_30_seconds' + | 'timerOption_30_seconds_abbreviated' + | 'timerOption_5_minutes' + | 'timerOption_5_minutes_abbreviated' + | 'timerOption_5_seconds' + | 'timerOption_5_seconds_abbreviated' + | 'timerOption_6_hours' + | 'timerOption_6_hours_abbreviated'; + function getTimerOptionName(time: number, unit: moment.DurationInputArg2) { return ( - window.i18n(['timerOption', time, unit].join('_') as LocalizerKeys) || + window.i18n(['timerOption', time, unit].join('_') as TimerOptionKey) || moment.duration(time, unit).humanize() ); } function getTimerOptionAbbreviated(time: number, unit: string) { - return window.i18n(['timerOption', time, unit, 'abbreviated'].join('_') as LocalizerKeys); + return window.i18n(['timerOption', time, unit, 'abbreviated'].join('_') as TimerOptionKey); } function getName(seconds = 0) { diff --git a/ts/session/types/PubKey.ts b/ts/session/types/PubKey.ts index 2320bf6463..8598339282 100644 --- a/ts/session/types/PubKey.ts +++ b/ts/session/types/PubKey.ts @@ -128,13 +128,13 @@ export class PubKey { * Returns a localized string of the error, or undefined in the given pubkey is valid. * * Note: this should be used when starting a conversation and we do not support starting conversation from scratch with a blinded sessionId. - * So if the given pubkey has a blinded prefix, this call will fail with a localized `invalidPubkeyFormat` error + * So if the given pubkey has a blinded prefix, this call will fail with a localized `accountIdErrorInvalid` error */ public static validateWithErrorNoBlinding(pubkey: string): string | undefined { // Check if it's hex const isHex = pubkey.replace(/[\s]*/g, '').match(/^[0-9a-fA-F]+$/); if (!isHex) { - return window.i18n('invalidSessionId'); + return window.i18n('accountIdErrorInvalid'); } // Check if the pubkey length is 33 and leading with 05 or of length 32 @@ -145,7 +145,7 @@ export class PubKey { // dev pubkey on testnet are now 66 chars too with the prefix, so every sessionID needs 66 chars and the prefix to be valid if (!isProdOrDevValid) { - return window.i18n('invalidPubkeyFormat'); + return window.i18n('accountIdErrorInvalid'); } return undefined; } diff --git a/ts/session/utils/Toast.tsx b/ts/session/utils/Toast.tsx index 1087345edd..464a9cba5d 100644 --- a/ts/session/utils/Toast.tsx +++ b/ts/session/utils/Toast.tsx @@ -57,14 +57,18 @@ export function pushToastSuccess( export function pushLoadAttachmentFailure(message?: string) { if (message) { - pushToastError('unableToLoadAttachment', `${window.i18n('unableToLoadAttachment')} ${message}`); + pushToastError('unableToLoadAttachment', `${window.i18n('attachmentsErrorLoad')} ${message}`); } else { - pushToastError('unableToLoadAttachment', window.i18n('unableToLoadAttachment')); + pushToastError('unableToLoadAttachment', window.i18n('attachmentsErrorLoad')); } } export function pushFileSizeError(limit: number, units: string) { - pushToastError('fileSizeWarning', window.i18n('fileSizeWarning'), `Max size: ${limit} ${units}`); + pushToastError( + 'fileSizeWarning', + window.i18n('attachmentsErrorSize'), + `Max size: ${limit} ${units}` + ); } export function pushFileSizeErrorAsByte(bytesCount: number) { @@ -79,42 +83,40 @@ export function pushFileSizeErrorAsByte(bytesCount: number) { } export function pushMultipleNonImageError() { - pushToastError( - 'cannotMixImageAndNonImageAttachments', - window.i18n('cannotMixImageAndNonImageAttachments') - ); + pushToastError('cannotMixImageAndNonImageAttachments', window.i18n('attachmentsErrorTypes')); } export function pushCannotMixError() { - pushToastError('oneNonImageAtATimeToast', window.i18n('oneNonImageAtATimeToast')); + pushToastError('oneNonImageAtATimeToast', window.i18n('attachmentsErrorTypes')); } export function pushMaximumAttachmentsError() { - pushToastError('maximumAttachments', window.i18n('maximumAttachments')); + pushToastError('maximumAttachments', window.i18n('attachmentsErrorNumber')); } export function pushMessageBodyMissing() { + // TODO: String localization - remove pushToastError('messageBodyMissing', window.i18n('messageBodyMissing')); } export function pushCopiedToClipBoard() { - pushToastInfo('copiedToClipboard', window.i18n('copiedToClipboard')); + pushToastInfo('copiedToClipboard', window.i18n('copied')); } export function pushRestartNeeded() { - pushToastInfo('restartNeeded', window.i18n('spellCheckDirty')); + pushToastInfo('restartNeeded', window.i18n('settingsRestartDescription')); } export function pushAlreadyMemberOpenGroup() { - pushToastInfo('publicChatExists', window.i18n('publicChatExists')); + pushToastInfo('publicChatExists', window.i18n('communityJoinedAlready')); } export function pushUserBanSuccess() { - pushToastSuccess('userBanned', window.i18n('userBanned')); + pushToastSuccess('userBanned', window.i18n('banUserBanned')); } export function pushUserBanFailure() { - pushToastError('userBanFailed', window.i18n('userBanFailed')); + pushToastError('userBanFailed', window.i18n('banErrorFailed')); } export function pushUserUnbanSuccess() { @@ -122,7 +124,7 @@ export function pushUserUnbanSuccess() { } export function pushUserUnbanFailure() { - pushToastError('userUnbanFailed', window.i18n('userUnbanFailed')); + pushToastError('userUnbanFailed', window.i18n('banUnbanErrorFailed')); } export function pushMessageDeleteForbidden() { @@ -130,15 +132,11 @@ export function pushMessageDeleteForbidden() { } export function pushUnableToCall() { - pushToastError('unableToCall', window.i18n('unableToCallTitle'), window.i18n('unableToCall')); + pushToastError('unableToCall', window.i18n('callsCannotStart'), window.i18n('callsCannotStart')); } export function pushedMissedCall(conversationName: string) { - pushToastInfo( - 'missedCall', - window.i18n('callMissedTitle'), - window.i18n('callMissed', [conversationName]) - ); + pushToastInfo('missedCall', window.i18n('callsMissedCallFrom', { name: conversationName })); } const openPermissionsSettings = () => { @@ -150,8 +148,8 @@ export function pushedMissedCallCauseOfPermission(conversationName: string) { const id = 'missedCallPermission'; toast.info( , @@ -159,19 +157,15 @@ export function pushedMissedCallCauseOfPermission(conversationName: string) { ); } -export function pushedMissedCallNotApproved(displayName: string) { - pushToastInfo( - 'missedCall', - window.i18n('callMissedTitle'), - window.i18n('callMissedNotApproved', [displayName]) - ); +export function pushedMissedCallNotApproved(name: string) { + pushToastInfo('missedCall', window.i18n('callsMissedCallFrom', { name })); } export function pushVideoCallPermissionNeeded() { pushToastInfo( 'videoCallPermissionNeeded', - window.i18n('cameraPermissionNeededTitle'), - window.i18n('cameraPermissionNeeded'), + window.i18n('callsPermissionsRequired'), + window.i18n('callsPermissionsRequiredDescription'), openPermissionsSettings ); } @@ -179,18 +173,18 @@ export function pushVideoCallPermissionNeeded() { export function pushAudioPermissionNeeded() { pushToastInfo( 'audioPermissionNeeded', - window.i18n('audioPermissionNeededTitle'), - window.i18n('audioPermissionNeeded'), + window.i18n('permissionsMicrophoneAccessRequired'), + window.i18n('permissionsMicrophoneAccessRequiredDesktop'), openPermissionsSettings ); } export function pushOriginalNotFound() { - pushToastError('originalMessageNotFound', window.i18n('originalMessageNotFound')); + pushToastError('originalMessageNotFound', window.i18n('messageErrorOriginal')); } export function pushTooManyMembers() { - pushToastError('tooManyMembers', window.i18n('closedGroupMaxSize')); + pushToastError('tooManyMembers', window.i18n('groupAddMemberMaximum')); } export function pushMessageRequestPending() { @@ -198,40 +192,27 @@ export function pushMessageRequestPending() { } export function pushUnblockToSend() { - pushToastInfo('unblockToSend', window.i18n('unblockToSend')); + pushToastInfo('unblockToSend', window.i18n('blockBlockedDescription')); } export function pushYouLeftTheGroup() { - pushToastError('youLeftTheGroup', window.i18n('youLeftTheGroup')); + pushToastError('youLeftTheGroup', window.i18n('groupMemberYouLeft')); } export function someDeletionsFailed() { pushToastWarning('deletionError', 'Deletion error'); } -export function pushDeleted(messageCount: number) { - pushToastSuccess( - 'deleted', - window.i18n('deleted', [messageCount.toString()]), - undefined, - 'check' - ); +export function pushDeleted() { + pushToastSuccess('deleted', window.i18n('deleteMessagesDeleted'), undefined, 'check'); } export function pushCannotRemoveCreatorFromGroup() { - pushToastWarning( - 'cannotRemoveCreatorFromGroup', - window.i18n('cannotRemoveCreatorFromGroup'), - window.i18n('cannotRemoveCreatorFromGroupDesc') - ); + pushToastWarning('cannotRemoveCreatorFromGroup', window.i18n('adminCannotBeRemoved')); } export function pushOnlyAdminCanRemove() { - pushToastInfo( - 'onlyAdminCanRemoveMembers', - window.i18n('onlyAdminCanRemoveMembers'), - window.i18n('onlyAdminCanRemoveMembersDesc') - ); + pushToastInfo('onlyAdminCanRemoveMembers', window.i18n('onlyAdminCanRemoveMembersDesc')); } export function pushFailedToAddAsModerator() { @@ -251,23 +232,23 @@ export function pushUserRemovedFromModerators() { } export function pushInvalidPubKey() { - pushToastSuccess('invalidPubKey', window.i18n('invalidPubkeyFormat')); + pushToastSuccess('invalidPubKey', window.i18n('accountIdErrorInvalid')); } export function pushNoCameraFound() { - pushToastWarning('noCameraFound', window.i18n('noCameraFound')); + pushToastWarning('noCameraFound', window.i18n('cameraErrorNotFound')); } export function pushNoAudioInputFound() { - pushToastWarning('noAudioInputFound', window.i18n('noAudioInputFound')); + pushToastWarning('noAudioInputFound', window.i18n('audioNoInput')); } export function pushNoAudioOutputFound() { - pushToastWarning('noAudioOutputFound', window.i18n('noAudioOutputFound')); + pushToastWarning('noAudioOutputFound', window.i18n('audioNoOutput')); } export function pushNoMediaUntilApproved() { - pushToastError('noMediaUntilApproved', window.i18n('noMediaUntilApproved')); + pushToastError('noMediaUntilApproved', window.i18n('messageRequestPendingDescription')); } export function pushMustBeApproved() { @@ -275,5 +256,5 @@ export function pushMustBeApproved() { } export function pushRateLimitHitReactions() { - pushToastInfo('reactRateLimit', '', window?.i18n?.('rateLimitReactMessage')); // because otherwise test fails + pushToastInfo('reactRateLimit', '', window?.i18n?.('emojiReactsCoolDown')); // because otherwise test fails } diff --git a/ts/state/selectors/messages.ts b/ts/state/selectors/messages.ts index b998bf7f70..df965799b4 100644 --- a/ts/state/selectors/messages.ts +++ b/ts/state/selectors/messages.ts @@ -37,7 +37,7 @@ export const useAuthorProfileName = (messageId: string): string | null => { const senderIsUs = msg.propsForMessage.sender === UserUtils.getOurPubKeyStrFromCache(); const authorProfileName = senderIsUs - ? window.i18n('you') + ? window.i18n('onionRoutingPathYou') : senderProps.nickname || senderProps.displayNameInProfile || window.i18n('anonymous'); return authorProfileName || window.i18n('unknown'); }; diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts index f8db935d2a..dbb17a2b80 100644 --- a/ts/state/selectors/search.ts +++ b/ts/state/selectors/search.ts @@ -73,11 +73,11 @@ export const getSearchResultsList = createSelector([getSearchResults], searchSta const { contactsAndGroups, messages } = searchState; const builtList: Array = []; if (contactsAndGroups.length) { - builtList.push(window.i18n('conversationsHeader', [`${contactsAndGroups.length}`])); + builtList.push(window.i18n('conversationsHeader', { count: contactsAndGroups.length })); builtList.push(...contactsAndGroups.map(m => ({ contactConvoId: m.id }))); } if (messages.length) { - builtList.push(window.i18n('searchMessagesHeader', [`${messages.length}`])); + builtList.push(window.i18n('messages')); builtList.push(...messages); } return builtList; diff --git a/ts/state/selectors/selectedConversation.ts b/ts/state/selectors/selectedConversation.ts index 070cfa1003..316b32b960 100644 --- a/ts/state/selectors/selectedConversation.ts +++ b/ts/state/selectors/selectedConversation.ts @@ -28,10 +28,10 @@ const getCurrentNotificationSettingText = (state: StateType): string | undefined switch (currentNotificationSetting) { case 'mentions_only': return window.i18n('notificationsMentionsOnly'); - case 'disabled': - return window.i18n('notificationsMute'); - case 'all': - default: + case 'disabled': + return window.i18n('notificationsMute'); + case 'all': + default: return window.i18n('notificationsAllMessages'); } }; diff --git a/ts/test/session/unit/selectors/conversations_test.ts b/ts/test/session/unit/selectors/conversations_test.ts index 86ab970ee6..47e44564b4 100644 --- a/ts/test/session/unit/selectors/conversations_test.ts +++ b/ts/test/session/unit/selectors/conversations_test.ts @@ -9,11 +9,19 @@ import { _getConversationComparator, _getSortedConversations, } from '../../../../state/selectors/conversations'; +import type { + GetMessageArgs, + LocalizerDictionary, + LocalizerToken, +} from '../../../../types/Localizer'; + +const i18n = ( + ...[token]: GetMessageArgs +) => token as any as R; describe('state/selectors/conversations', () => { describe('#getSortedConversationsList', () => { it('sorts conversations based on timestamp then by intl-friendly title', () => { - const i18n = (key: string) => key; const data: ConversationLookupType = { id1: { id: 'id1', @@ -145,7 +153,6 @@ describe('state/selectors/conversations', () => { describe('#getSortedConversationsWithPinned', () => { it('sorts conversations based on pin, timestamp then by intl-friendly title', () => { - const i18n = (key: string) => key; const data: ConversationLookupType = { id1: { id: 'id1', diff --git a/ts/themes/constants/colors.tsx b/ts/themes/constants/colors.tsx index ad7f68a7de..013412499f 100644 --- a/ts/themes/constants/colors.tsx +++ b/ts/themes/constants/colors.tsx @@ -249,7 +249,7 @@ export type StyleSessionSwitcher = { export const getThemeColors = (): Array => [ { id: 'classic-dark', - title: window.i18n('classicDarkThemeTitle'), + title: window.i18n('appearanceThemesClassicDark'), style: { background: THEMES.CLASSIC_DARK.COLOR0, border: THEMES.CLASSIC_DARK.COLOR3, @@ -259,7 +259,7 @@ export const getThemeColors = (): Array => [ }, { id: 'classic-light', - title: window.i18n('classicLightThemeTitle'), + title: window.i18n('appearanceThemesClassicLight'), style: { background: THEMES.CLASSIC_LIGHT.COLOR6, border: THEMES.CLASSIC_LIGHT.COLOR3, @@ -269,7 +269,7 @@ export const getThemeColors = (): Array => [ }, { id: 'ocean-dark', - title: window.i18n('oceanDarkThemeTitle'), + title: window.i18n('appearanceThemesOceanDark'), style: { background: THEMES.OCEAN_DARK.COLOR2, border: THEMES.OCEAN_DARK.COLOR4, @@ -279,7 +279,7 @@ export const getThemeColors = (): Array => [ }, { id: 'ocean-light', - title: window.i18n('oceanLightThemeTitle'), + title: window.i18n('appearanceThemesOceanLight'), style: { background: THEMES.OCEAN_LIGHT.COLOR7!, border: THEMES.OCEAN_LIGHT.COLOR3, diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index bc2a60e7ce..ea948a8398 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -220,9 +220,8 @@ export function areAllAttachmentsVisual(attachments?: Array): bo } export function getAlt(attachment: AttachmentType): string { - return isVideoAttachment(attachment) - ? window.i18n('videoAttachmentAlt') - : window.i18n('imageAttachmentAlt'); + // TODO: Rework when the alt text is available + return isVideoAttachment(attachment) ? '' : ''; } // Migration-related attachment stuff diff --git a/ts/updater/common.ts b/ts/updater/common.ts index 26096ba685..4218fd7ca8 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -23,10 +23,10 @@ export async function showDownloadUpdateDialog( const LATER_BUTTON = 1; const options = { type: 'info' as const, - buttons: [messages.autoUpdateDownloadButtonLabel, messages.autoUpdateLaterButtonLabel], - title: messages.autoUpdateNewVersionTitle, - message: messages.autoUpdateNewVersionMessage, - detail: messages.autoUpdateDownloadInstructions, + buttons: [messages.download, messages.later], + title: messages.updateSession, + message: messages.updateNewVersionDescription, + detail: messages.updateNewVersionDescription, defaultId: LATER_BUTTON, cancelId: DOWNLOAD_BUTTON, }; @@ -44,10 +44,10 @@ export async function showUpdateDialog( const LATER_BUTTON = 1; const options = { type: 'info' as const, - buttons: [messages.autoUpdateRestartButtonLabel, messages.autoUpdateLaterButtonLabel], - title: messages.autoUpdateNewVersionTitle, - message: messages.autoUpdateDownloadedMessage, - detail: messages.autoUpdateNewVersionInstructions, + buttons: [messages.restart, messages.later], + title: messages.updateSession, + message: messages.updateDownloaded, + detail: messages.updateDownloaded, defaultId: LATER_BUTTON, cancelId: RESTART_BUTTON, }; @@ -60,8 +60,8 @@ export async function showCannotUpdateDialog(mainWindow: BrowserWindow, messages const options = { type: 'error' as const, buttons: [messages.ok], - title: messages.cannotUpdate, - message: messages.cannotUpdateDetail, + title: messages.updateError, + message: messages.updateErrorDescription, }; await dialog.showMessageBox(mainWindow, options); } diff --git a/ts/util/notifications.ts b/ts/util/notifications.ts index f4fcf4b085..56f100370a 100644 --- a/ts/util/notifications.ts +++ b/ts/util/notifications.ts @@ -165,7 +165,7 @@ function update(forceRefresh = false) { // e.g. Russian: // http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html const newMessageCountLabel = `${messagesNotificationCount} ${ - messagesNotificationCount === 1 ? window.i18n('newMessage') : window.i18n('newMessages') + messagesNotificationCount === 1 ? window.i18n('messageNew') : window.i18n('messageNewMessages') }`; if (!currentNotifications.length) { @@ -194,9 +194,9 @@ function update(forceRefresh = false) { // eslint-disable-next-line prefer-destructuring iconUrl = lastNotification.iconUrl; if (messagesNotificationCount === 1) { - message = `${window.i18n('notificationFrom')} ${lastMessageTitle}`; + message = `${window.i18n('from')} ${lastMessageTitle}`; } else { - message = window.i18n('notificationMostRecentFrom', [lastMessageTitle]); + message = window.i18n('notificationsMostRecent', { name: lastMessageTitle }); } break; } @@ -208,7 +208,9 @@ function update(forceRefresh = false) { message = lastNotification.message; } else { title = newMessageCountLabel; - message = `${window.i18n('notificationMostRecent')} ${lastNotification.message}`; + message = `${window.i18n('notificationsMostRecent', { name: title })} ${ + lastNotification.message + }`; } // eslint-disable-next-line prefer-destructuring iconUrl = lastNotification.iconUrl; @@ -219,7 +221,7 @@ function update(forceRefresh = false) { const shouldHideExpiringMessageBody = lastNotification.isExpiringMessage && isMacOS(); if (shouldHideExpiringMessageBody) { - message = window.i18n('newMessage'); + message = window.i18n('messageNew'); } window.drawAttention(); diff --git a/ts/util/passwordUtils.ts b/ts/util/passwordUtils.ts index d6c12d1e03..84704d89ba 100644 --- a/ts/util/passwordUtils.ts +++ b/ts/util/passwordUtils.ts @@ -20,22 +20,22 @@ export const matchesHash = (phrase: string | null, hash: string) => export const validatePassword = (phrase: string) => { if (typeof phrase !== 'string') { - return window?.i18n ? window?.i18n('passwordTypeError') : ERRORS.TYPE; + return window?.i18n ? window?.i18n('passwordError') : ERRORS.TYPE; } const trimmed = phrase.trim(); if (trimmed.length === 0) { - return window?.i18n ? window?.i18n('noGivenPassword') : ERRORS.LENGTH; + return window?.i18n ? window?.i18n('passwordCreate') : ERRORS.LENGTH; } if (trimmed.length < 6 || trimmed.length > MAX_PASSWORD_LENGTH) { - return window?.i18n ? window?.i18n('passwordLengthError') : ERRORS.LENGTH; + return window?.i18n ? window?.i18n('passwordErrorLength') : ERRORS.LENGTH; } // Restrict characters to letters, numbers and symbols const characterRegex = /^[a-zA-Z0-9-!?/\\()._`~@#$%^&*+=[\]{}|<>,;: ]+$/; if (!characterRegex.test(trimmed)) { - return window?.i18n ? window?.i18n('passwordCharacterError') : ERRORS.CHARACTER; + return window?.i18n ? window?.i18n('passwordError') : ERRORS.CHARACTER; } return null; From 2fa5d3ecccf40c5a7c0e581a7e77b48ae7c54bb3 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 14:18:33 +1100 Subject: [PATCH 15/93] feat: reword SubtitleNotification to use changed localized strings and new i18n component --- .../conversation/SubtleNotification.tsx | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/ts/components/conversation/SubtleNotification.tsx b/ts/components/conversation/SubtleNotification.tsx index 1a1730b9d5..adad78189e 100644 --- a/ts/components/conversation/SubtleNotification.tsx +++ b/ts/components/conversation/SubtleNotification.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { useIsIncomingRequest } from '../../hooks/useParamSelector'; @@ -13,8 +13,7 @@ import { useSelectedIsNoteToSelf, useSelectedNicknameOrProfileNameOrShortenedPubkey, } from '../../state/selectors/selectedConversation'; -import { LocalizerKeys } from '../../types/LocalizerKeys'; -import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer'; +import { I18n } from '../basic/I18n'; const Container = styled.div` display: flex; @@ -47,7 +46,7 @@ export const ConversationRequestExplanation = () => { return ( - {window.i18n('respondingToRequestWarning')} + {window.i18n('messageRequestsAcceptDescription')} ); }; @@ -64,27 +63,31 @@ export const NoMessageInConversation = () => { const canWrite = useSelector(getSelectedCanWrite); const privateBlindedAndBlockingMsgReqs = useSelectedHasDisabledBlindedMsgRequests(); // TODOLATER use this selector across the whole application (left pane excluded) - const nameToRender = useSelectedNicknameOrProfileNameOrShortenedPubkey(); + const name = useSelectedNicknameOrProfileNameOrShortenedPubkey(); + + const messageText = useMemo(() => { + if (isMe) { + return ; + } + + if (canWrite) { + return ; + } - if (!selectedConversation || hasMessage) { - return null; - } - let localizedKey: LocalizerKeys = 'noMessagesInEverythingElse'; - if (!canWrite) { if (privateBlindedAndBlockingMsgReqs) { - localizedKey = 'noMessagesInBlindedDisabledMsgRequests'; - } else { - localizedKey = 'noMessagesInReadOnly'; + return ; } - } else if (isMe) { - localizedKey = 'noMessagesInNoteToSelf'; + + return ; + }, [isMe, canWrite, privateBlindedAndBlockingMsgReqs, name]); + + if (!selectedConversation || hasMessage) { + return null; } return ( - - - + {messageText} ); }; From f15df7949de19a6aaada12bef263c502a29534ef Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 14:19:04 +1100 Subject: [PATCH 16/93] feat: update CallNotificaiton strings and enforce type safety --- .../notification-bubble/CallNotification.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx b/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx index ea983a8961..a3240401ef 100644 --- a/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx +++ b/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx @@ -10,24 +10,24 @@ import { useSelectedDisplayNameInProfile, useSelectedNickname, } from '../../../../../state/selectors/selectedConversation'; -import { LocalizerKeys } from '../../../../../types/LocalizerKeys'; +import { LocalizerToken } from '../../../../../types/Localizer'; import { SessionIconType } from '../../../../icon'; import { ExpirableReadableMessage } from '../ExpirableReadableMessage'; import { NotificationBubble } from './NotificationBubble'; type StyleType = Record< CallNotificationType, - { notificationTextKey: LocalizerKeys; iconType: SessionIconType; iconColor: string } + { notificationTextKey: LocalizerToken; iconType: SessionIconType; iconColor: string } >; -const style: StyleType = { +const style = { 'missed-call': { - notificationTextKey: 'callMissed', + notificationTextKey: 'callsMissedCallFrom', iconType: 'callMissed', iconColor: 'var(--danger-color)', }, 'started-call': { - notificationTextKey: 'startedACall', + notificationTextKey: 'callsYouCalled', iconType: 'callOutgoing', iconColor: 'inherit', }, @@ -36,7 +36,7 @@ const style: StyleType = { iconType: 'callIncoming', iconColor: 'inherit', }, -}; +} satisfies StyleType; export const CallNotification = (props: PropsForCallNotification) => { const { messageId, notificationType } = props; @@ -49,12 +49,11 @@ export const CallNotification = (props: PropsForCallNotification) => { nickname || displayNameInProfile || (selectedConvoId && PubKey.shorten(selectedConvoId)); const styleItem = style[notificationType]; - const notificationText = window.i18n(styleItem.notificationTextKey, [ - displayName || window.i18n('unknown'), - ]); - if (!window.i18n(styleItem.notificationTextKey)) { - throw new Error(`invalid i18n key ${styleItem.notificationTextKey}`); - } + + const notificationText = window.i18n(styleItem.notificationTextKey, { + name: displayName ?? window.i18n('unknown'), + }); + const iconType = styleItem.iconType; const iconColor = styleItem.iconColor; From 325fb5f27e43e07391f7eda538ff66c2cc0da779 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 14:22:14 +1100 Subject: [PATCH 17/93] feat: change localized strings in ReactionPopup and refactor logic for string selection --- .../message/reactions/ReactionPopup.tsx | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/ts/components/conversation/message/reactions/ReactionPopup.tsx b/ts/components/conversation/message/reactions/ReactionPopup.tsx index cefd352cf6..4496a6383c 100644 --- a/ts/components/conversation/message/reactions/ReactionPopup.tsx +++ b/ts/components/conversation/message/reactions/ReactionPopup.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import { Data } from '../../../../data/data'; import { PubKey } from '../../../../session/types/PubKey'; import { isDarkTheme } from '../../../../state/selectors/theme'; +import { LocalizerToken } from '../../../../types/Localizer'; import { nativeEmojiData } from '../../../../util/emoji'; import { findAndFormatContact } from '../../../../models/message'; @@ -87,7 +88,7 @@ const generateContactsString = async ( }); if (meIndex >= 0) { results.splice(meIndex, 1); - results = [window.i18n('you'), ...results]; + results = [window.i18n('onionRoutingPathYou'), ...results]; } if (results && results.length > 0) { return results; @@ -104,33 +105,39 @@ const Contacts = (contacts: Array, count: number) => { } const reactors = contacts.length; - if (reactors === 1 || reactors === 2 || reactors === 3) { - return ( - - {window.i18n( - reactors === 1 - ? 'reactionPopupOne' - : reactors === 2 - ? 'reactionPopupTwo' - : 'reactionPopupThree', - contacts - )}{' '} - {window.i18n('reactionPopup')} - - ); + + let reactionPopupKey: LocalizerToken; + switch (reactors) { + case 1: + reactionPopupKey = 'reactionPopupOne'; + break; + case 2: + reactionPopupKey = 'reactionPopupTwo'; + break; + case 3: + reactionPopupKey = 'reactionPopupThree'; + break; + default: + reactionPopupKey = 'reactionPopupMany'; } - if (reactors > 3) { - return ( - - {window.i18n('reactionPopupMany', [contacts[0], contacts[1], contacts[3]])}{' '} + + return ( + + {window.i18n(reactionPopupKey, { + name: contacts[0], + name2: contacts[1], + name3: contacts[2], + })}{' '} + {reactors > 3 ? ( - {window.i18n(reactors === 4 ? 'otherSingular' : 'otherPlural', [`${count - 3}`])} - {' '} - {window.i18n('reactionPopup')} - - ); - } - return null; + {window.i18n(reactors === 4 ? 'otherSingular' : 'otherPlural', { + number: `${count - 3}`, + })} + + ) : null} + {window.i18n('reactionPopup')} + + ); }; type Props = { From b7542a4afaa6f28395ecf5251024c00227a22170 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 14:36:49 +1100 Subject: [PATCH 18/93] feat: create strict typing for localization --- ts/node/locale.ts | 13 +- ts/types/LocalizerKeys.ts | 581 -------------------------------------- ts/types/Util.ts | 4 +- 3 files changed, 7 insertions(+), 591 deletions(-) delete mode 100644 ts/types/LocalizerKeys.ts diff --git a/ts/node/locale.ts b/ts/node/locale.ts index 8737eed3bf..d716f0b5c9 100644 --- a/ts/node/locale.ts +++ b/ts/node/locale.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import _ from 'lodash'; import path from 'path'; +import type { LocalizerDictionary } from '../types/Localizer'; import { getAppRootPath } from './getRootPath'; function normalizeLocaleName(locale: string) { @@ -14,20 +15,18 @@ function normalizeLocaleName(locale: string) { return locale; } -function getLocaleMessages(locale: string): LocaleMessagesType { +function getLocaleMessages(locale: string): LocalizerDictionary { const onDiskLocale = locale.replace('-', '_'); const targetFile = path.join(getAppRootPath(), '_locales', onDiskLocale, 'messages.json'); return JSON.parse(fs.readFileSync(targetFile, 'utf-8')); } -export type LocaleMessagesType = Record; -export type LocaleMessagesWithNameType = { messages: LocaleMessagesType; name: string }; -export function load({ - appLocale, - logger, -}: { appLocale?: string; logger?: any } = {}): LocaleMessagesWithNameType { +export function load({ appLocale, logger }: { appLocale?: string; logger?: any } = {}): { + name: string; + messages: LocalizerDictionary; +} { if (!appLocale) { throw new TypeError('`appLocale` is required'); } diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts deleted file mode 100644 index 3b31283edd..0000000000 --- a/ts/types/LocalizerKeys.ts +++ /dev/null @@ -1,581 +0,0 @@ -export type LocalizerKeys = - | 'ByUsingThisService...' - | 'about' - | 'accept' - | 'activeMembers' - | 'add' - | 'addACaption' - | 'addAsModerator' - | 'addModerator' - | 'addModerators' - | 'addingContacts' - | 'allUsersAreRandomly...' - | 'anonymous' - | 'answeredACall' - | 'appMenuHide' - | 'appMenuHideOthers' - | 'appMenuQuit' - | 'appMenuUnhide' - | 'appearanceSettingsTitle' - | 'areYouSureClearDevice' - | 'areYouSureDeleteDeviceOnly' - | 'areYouSureDeleteEntireAccount' - | 'audio' - | 'audioMessageAutoplayDescription' - | 'audioMessageAutoplayTitle' - | 'audioNotificationsSettingsTitle' - | 'audioPermissionNeeded' - | 'audioPermissionNeededTitle' - | 'autoUpdateDownloadButtonLabel' - | 'autoUpdateDownloadInstructions' - | 'autoUpdateDownloadedMessage' - | 'autoUpdateLaterButtonLabel' - | 'autoUpdateNewVersionInstructions' - | 'autoUpdateNewVersionMessage' - | 'autoUpdateNewVersionTitle' - | 'autoUpdateRestartButtonLabel' - | 'autoUpdateSettingDescription' - | 'autoUpdateSettingTitle' - | 'banUser' - | 'banUserAndDeleteAll' - | 'beginYourSession' - | 'blindedMsgReqsSettingDesc' - | 'blindedMsgReqsSettingTitle' - | 'block' - | 'blocked' - | 'blockedSettingsTitle' - | 'callMediaPermissionsDescription' - | 'callMediaPermissionsDialogContent' - | 'callMediaPermissionsDialogTitle' - | 'callMediaPermissionsTitle' - | 'callMissed' - | 'callMissedCausePermission' - | 'callMissedNotApproved' - | 'callMissedTitle' - | 'cameraPermissionNeeded' - | 'cameraPermissionNeededTitle' - | 'cancel' - | 'cannotMixImageAndNonImageAttachments' - | 'cannotRemoveCreatorFromGroup' - | 'cannotRemoveCreatorFromGroupDesc' - | 'cannotUpdate' - | 'cannotUpdateDetail' - | 'changeAccountPasswordDescription' - | 'changeAccountPasswordTitle' - | 'changeNickname' - | 'changeNicknameMessage' - | 'changePassword' - | 'changePasswordInvalid' - | 'changePasswordTitle' - | 'changePasswordToastDescription' - | 'chooseAnAction' - | 'classicDarkThemeTitle' - | 'classicLightThemeTitle' - | 'clear' - | 'clearAll' - | 'clearAllConfirmationBody' - | 'clearAllConfirmationTitle' - | 'clearAllData' - | 'clearAllReactions' - | 'clearDataSettingsTitle' - | 'clearDevice' - | 'clearNickname' - | 'clickToTrustContact' - | 'close' - | 'closedGroupInviteFailMessage' - | 'closedGroupInviteFailMessagePlural' - | 'closedGroupInviteFailTitle' - | 'closedGroupInviteFailTitlePlural' - | 'closedGroupInviteOkText' - | 'closedGroupInviteSuccessMessage' - | 'closedGroupInviteSuccessTitle' - | 'closedGroupInviteSuccessTitlePlural' - | 'closedGroupMaxSize' - | 'confirm' - | 'confirmNewPassword' - | 'confirmPassword' - | 'connectToServerFail' - | 'connectToServerSuccess' - | 'connectingToServer' - | 'contactAvatarAlt' - | 'contactsHeader' - | 'contextMenuNoSuggestions' - | 'continue' - | 'continueYourSession' - | 'conversationsHeader' - | 'conversationsSettingsTitle' - | 'copiedToClipboard' - | 'copyErrorAndQuit' - | 'copyMessage' - | 'copyOpenGroupURL' - | 'copySessionID' - | 'couldntFindServerMatching' - | 'create' - | 'createAccount' - | 'createClosedGroupNamePrompt' - | 'createClosedGroupPlaceholder' - | 'createConversationNewContact' - | 'createConversationNewGroup' - | 'createGroup' - | 'createPassword' - | 'createSessionID' - | 'databaseError' - | 'debugLog' - | 'debugLogExplanation' - | 'decline' - | 'declineRequestMessage' - | 'delete' - | 'deleteAccountFromLogin' - | 'deleteAccountWarning' - | 'deleteContactConfirmation' - | 'deleteConversation' - | 'deleteConversationConfirmation' - | 'deleteConversationFailed' - | 'deleteConversationFailedPleaseTryAgain' - | 'deleteForEveryone' - | 'deleteJustForMe' - | 'deleteMessageQuestion' - | 'deleteMessages' - | 'deleteMessagesConfirmation' - | 'deleteMessagesQuestion' - | 'deleted' - | 'destination' - | 'device' - | 'deviceOnly' - | 'dialogClearAllDataDeletionFailedDesc' - | 'dialogClearAllDataDeletionFailedMultiple' - | 'dialogClearAllDataDeletionFailedTitle' - | 'dialogClearAllDataDeletionFailedTitleQuestion' - | 'dialogClearAllDataDeletionQuestion' - | 'disabledDisappearingMessages' - | 'disappearingMessages' - | 'disappearingMessagesDisabled' - | 'disappearingMessagesModeAfterRead' - | 'disappearingMessagesModeAfterReadSubtitle' - | 'disappearingMessagesModeAfterSend' - | 'disappearingMessagesModeAfterSendSubtitle' - | 'disappearingMessagesModeLabel' - | 'disappearingMessagesModeLegacy' - | 'disappearingMessagesModeLegacySubtitle' - | 'disappearingMessagesModeOff' - | 'disappearingMessagesModeOutdated' - | 'disappears' - | 'displayName' - | 'displayNameEmpty' - | 'displayNameTooLong' - | 'document' - | 'documents' - | 'documentsEmptyState' - | 'done' - | 'downloadAttachment' - | 'duration' - | 'editGroup' - | 'editGroupName' - | 'editMenuCopy' - | 'editMenuCut' - | 'editMenuDeleteContact' - | 'editMenuDeleteGroup' - | 'editMenuPaste' - | 'editMenuRedo' - | 'editMenuSelectAll' - | 'editMenuUndo' - | 'editProfileModalTitle' - | 'emptyGroupNameError' - | 'enable' - | 'endCall' - | 'enterAnOpenGroupURL' - | 'enterDisplayName' - | 'enterKeySettingDescription' - | 'enterKeySettingTitle' - | 'enterNewLineDescription' - | 'enterNewPassword' - | 'enterPassword' - | 'enterRecoveryPhrase' - | 'enterSendNewMessageDescription' - | 'enterSessionID' - | 'enterSessionIDOfRecipient' - | 'enterSessionIDOrONSName' - | 'entireAccount' - | 'error' - | 'establishingConnection' - | 'expandedReactionsText' - | 'expirationDuration' - | 'expirationType' - | 'failed' - | 'failedResolveOns' - | 'failedToAddAsModerator' - | 'failedToRemoveFromModerator' - | 'failedToSendMessage' - | 'faq' - | 'fileId' - | 'fileSize' - | 'fileSizeWarning' - | 'fileType' - | 'followSetting' - | 'followSettingDisabled' - | 'followSettingTimeAndType' - | 'from' - | 'getStarted' - | 'goToReleaseNotes' - | 'goToSupportPage' - | 'groupMembers' - | 'groupNamePlaceholder' - | 'helpSettingsTitle' - | 'helpUsTranslateSession' - | 'hide' - | 'hideBanner' - | 'hideConversation' - | 'hideMenuBarDescription' - | 'hideMenuBarTitle' - | 'hideNoteToSelfConfirmation' - | 'hideRequestBanner' - | 'hideRequestBannerDescription' - | 'iAmSure' - | 'image' - | 'imageAttachmentAlt' - | 'imageCaptionIconAlt' - | 'incomingCallFrom' - | 'incomingError' - | 'invalidGroupNameTooLong' - | 'invalidGroupNameTooShort' - | 'invalidNumberError' - | 'invalidOldPassword' - | 'invalidOpenGroupUrl' - | 'invalidPassword' - | 'invalidPubkeyFormat' - | 'invalidSessionId' - | 'inviteContacts' - | 'join' - | 'joinACommunity' - | 'joinOpenGroup' - | 'joinOpenGroupAfterInvitationConfirmationDesc' - | 'joinOpenGroupAfterInvitationConfirmationTitle' - | 'joinedTheGroup' - | 'keepDisabled' - | 'kickedFromTheGroup' - | 'learnMore' - | 'leave' - | 'leaveAndRemoveForEveryone' - | 'leaveCommunity' - | 'leaveCommunityFailed' - | 'leaveCommunityFailedPleaseTryAgain' - | 'leaveGroup' - | 'leaveGroupConfirmation' - | 'leaveGroupConfirmationAdmin' - | 'leaveGroupConfirmationOnlyAdmin' - | 'leaveGroupConfirmationOnlyAdminWarning' - | 'leaveGroupConrirmationOnlyAdminLegacy' - | 'leaveGroupFailed' - | 'leaveGroupFailedPleaseTryAgain' - | 'leaving' - | 'leftTheGroup' - | 'lightboxImageAlt' - | 'linkDevice' - | 'linkPreviewDescription' - | 'linkPreviewsConfirmMessage' - | 'linkPreviewsTitle' - | 'linkVisitWarningMessage' - | 'linkVisitWarningTitle' - | 'loading' - | 'mainMenuEdit' - | 'mainMenuFile' - | 'mainMenuHelp' - | 'mainMenuView' - | 'mainMenuWindow' - | 'markAllAsRead' - | 'markUnread' - | 'matchThemeSystemSettingDescription' - | 'matchThemeSystemSettingTitle' - | 'maxPasswordAttempts' - | 'maximumAttachments' - | 'media' - | 'mediaEmptyState' - | 'mediaMessage' - | 'mediaPermissionsDescription' - | 'mediaPermissionsTitle' - | 'members' - | 'message' - | 'messageBody' - | 'messageBodyMissing' - | 'messageDeletedPlaceholder' - | 'messageDeletionForbidden' - | 'messageHash' - | 'messageInfo' - | 'messageRequestAccepted' - | 'messageRequestAcceptedOurs' - | 'messageRequestAcceptedOursNoName' - | 'messageRequestPending' - | 'messageRequests' - | 'messageWillDisappear' - | 'messagesHeader' - | 'moreInformation' - | 'multipleJoinedTheGroup' - | 'multipleKickedFromTheGroup' - | 'multipleLeftTheGroup' - | 'mustBeApproved' - | 'nameAndMessage' - | 'nameOnly' - | 'newMessage' - | 'newMessages' - | 'next' - | 'nicknamePlaceholder' - | 'noAudioInputFound' - | 'noAudioOutputFound' - | 'noBlockedContacts' - | 'noCameraFound' - | 'noContactsForGroup' - | 'noContactsToAdd' - | 'noGivenPassword' - | 'noMediaUntilApproved' - | 'noMembersInThisGroup' - | 'noMessageRequestsPending' - | 'noMessagesInBlindedDisabledMsgRequests' - | 'noMessagesInEverythingElse' - | 'noMessagesInNoteToSelf' - | 'noMessagesInReadOnly' - | 'noModeratorsToRemove' - | 'noNameOrMessage' - | 'noSearchResults' - | 'notApplicable' - | 'noteToSelf' - | 'notificationForConvo' - | 'notificationForConvo_all' - | 'notificationForConvo_disabled' - | 'notificationForConvo_mentions_only' - | 'notificationFrom' - | 'notificationMostRecent' - | 'notificationMostRecentFrom' - | 'notificationPreview' - | 'notificationSettingsDialog' - | 'notificationSubtitle' - | 'notificationsSettingsContent' - | 'notificationsSettingsTitle' - | 'oceanDarkThemeTitle' - | 'oceanLightThemeTitle' - | 'offline' - | 'ok' - | 'oneNonImageAtATimeToast' - | 'onionPathIndicatorDescription' - | 'onionPathIndicatorTitle' - | 'onlyAdminCanRemoveMembers' - | 'onlyAdminCanRemoveMembersDesc' - | 'onlyGroupAdminsCanChange' - | 'open' - | 'openGroupInvitation' - | 'openGroupURL' - | 'openMessageRequestInbox' - | 'openMessageRequestInboxDescription' - | 'or' - | 'orJoinOneOfThese' - | 'originalMessageNotFound' - | 'otherPlural' - | 'otherSingular' - | 'password' - | 'passwordCharacterError' - | 'passwordLengthError' - | 'passwordTypeError' - | 'passwordViewTitle' - | 'passwordsDoNotMatch' - | 'permissionsSettingsTitle' - | 'photo' - | 'pickClosedGroupMember' - | 'pinConversation' - | 'pleaseWaitOpenAndOptimizeDb' - | 'previewThumbnail' - | 'primaryColor' - | 'primaryColorBlue' - | 'primaryColorGreen' - | 'primaryColorOrange' - | 'primaryColorPink' - | 'primaryColorPurple' - | 'primaryColorRed' - | 'primaryColorYellow' - | 'privacySettingsTitle' - | 'pruneSettingDescription' - | 'pruneSettingTitle' - | 'publicChatExists' - | 'quoteThumbnailAlt' - | 'rateLimitReactMessage' - | 'reactionListCountPlural' - | 'reactionListCountSingular' - | 'reactionNotification' - | 'reactionPopup' - | 'reactionPopupMany' - | 'reactionPopupOne' - | 'reactionPopupThree' - | 'reactionPopupTwo' - | 'read' - | 'readReceiptSettingDescription' - | 'readReceiptSettingTitle' - | 'received' - | 'recoveryPhrase' - | 'recoveryPhraseEmpty' - | 'recoveryPhraseRevealButtonText' - | 'recoveryPhraseRevealMessage' - | 'recoveryPhraseSavePromptMain' - | 'recoveryPhraseSecureTitle' - | 'remove' - | 'removeAccountPasswordDescription' - | 'removeAccountPasswordTitle' - | 'removeFromModerators' - | 'removeModerators' - | 'removePassword' - | 'removePasswordInvalid' - | 'removePasswordTitle' - | 'removePasswordToastDescription' - | 'removeResidueMembers' - | 'replyToMessage' - | 'replyingToMessage' - | 'reportIssue' - | 'requestsPlaceholder' - | 'requestsSubtitle' - | 'resend' - | 'resolution' - | 'respondingToRequestWarning' - | 'restoreUsingRecoveryPhrase' - | 'ringing' - | 'save' - | 'saveLogToDesktop' - | 'saved' - | 'savedMessages' - | 'savedTheFile' - | 'searchFor...' - | 'searchForContactsOnly' - | 'searchMessagesHeader' - | 'selectMessage' - | 'sendFailed' - | 'sendMessage' - | 'sendRecoveryPhraseMessage' - | 'sendRecoveryPhraseTitle' - | 'sending' - | 'sent' - | 'serverId' - | 'sessionMessenger' - | 'set' - | 'setAccountPasswordDescription' - | 'setAccountPasswordTitle' - | 'setDisplayPicture' - | 'setPassword' - | 'setPasswordFail' - | 'setPasswordInvalid' - | 'setPasswordTitle' - | 'setPasswordToastDescription' - | 'settingAppliesToEveryone' - | 'settingAppliesToYourMessages' - | 'settingsHeader' - | 'shareBugDetails' - | 'show' - | 'showDebugLog' - | 'showRecoveryPhrase' - | 'showRecoveryPhrasePasswordRequest' - | 'showUserDetails' - | 'someOfYourDeviceUseOutdatedVersion' - | 'spellCheckDescription' - | 'spellCheckDirty' - | 'spellCheckTitle' - | 'stagedImageAttachment' - | 'stagedPreviewThumbnail' - | 'startConversation' - | 'startInTrayDescription' - | 'startInTrayTitle' - | 'startNewConversationBy...' - | 'startedACall' - | 'support' - | 'surveyTitle' - | 'themesSettingTitle' - | 'theyChangedTheTimer' - | 'theyChangedTheTimerLegacy' - | 'theyDisabledTheirDisappearingMessages' - | 'theySetTheirDisappearingMessages' - | 'thisMonth' - | 'thisWeek' - | 'timer' - | 'timerModeRead' - | 'timerModeSent' - | 'timerOption_0_seconds' - | 'timerOption_0_seconds_abbreviated' - | 'timerOption_10_seconds' - | 'timerOption_10_seconds_abbreviated' - | 'timerOption_12_hours' - | 'timerOption_12_hours_abbreviated' - | 'timerOption_1_day' - | 'timerOption_1_day_abbreviated' - | 'timerOption_1_hour' - | 'timerOption_1_hour_abbreviated' - | 'timerOption_1_minute' - | 'timerOption_1_minute_abbreviated' - | 'timerOption_1_week' - | 'timerOption_1_week_abbreviated' - | 'timerOption_2_weeks' - | 'timerOption_2_weeks_abbreviated' - | 'timerOption_30_minutes' - | 'timerOption_30_minutes_abbreviated' - | 'timerOption_30_seconds' - | 'timerOption_30_seconds_abbreviated' - | 'timerOption_5_minutes' - | 'timerOption_5_minutes_abbreviated' - | 'timerOption_5_seconds' - | 'timerOption_5_seconds_abbreviated' - | 'timerOption_6_hours' - | 'timerOption_6_hours_abbreviated' - | 'timerSetTo' - | 'titleIsNow' - | 'to' - | 'today' - | 'tookAScreenshot' - | 'trimDatabase' - | 'trimDatabaseConfirmationBody' - | 'trimDatabaseDescription' - | 'trustThisContactDialogDescription' - | 'trustThisContactDialogTitle' - | 'tryAgain' - | 'typeInOldPassword' - | 'typingAlt' - | 'typingIndicatorsSettingDescription' - | 'typingIndicatorsSettingTitle' - | 'unableToCall' - | 'unableToCallTitle' - | 'unableToLoadAttachment' - | 'unbanUser' - | 'unblock' - | 'unblockGroupToSend' - | 'unblockToSend' - | 'unblocked' - | 'unknown' - | 'unknownCountry' - | 'unknownError' - | 'unpinConversation' - | 'unreadMessages' - | 'updateGroupDialogTitle' - | 'updatedTheGroup' - | 'userAddedToModerators' - | 'userBanFailed' - | 'userBanned' - | 'userRemovedFromModerators' - | 'userUnbanFailed' - | 'userUnbanned' - | 'video' - | 'videoAttachmentAlt' - | 'viewMenuResetZoom' - | 'viewMenuToggleDevTools' - | 'viewMenuToggleFullScreen' - | 'viewMenuZoomIn' - | 'viewMenuZoomOut' - | 'voiceMessage' - | 'welcomeToYourSession' - | 'windowMenuClose' - | 'windowMenuMinimize' - | 'windowMenuZoom' - | 'yesterday' - | 'you' - | 'youChangedTheTimer' - | 'youChangedTheTimerLegacy' - | 'youDisabledDisappearingMessages' - | 'youDisabledYourDisappearingMessages' - | 'youGotKickedFromGroup' - | 'youHaveANewFriendRequest' - | 'youLeftTheGroup' - | 'youSetYourDisappearingMessages' - | 'yourSessionID' - | 'yourUniqueSessionID' - | 'zoomFactorSettingTitle'; diff --git a/ts/types/Util.ts b/ts/types/Util.ts index 76f1d68008..a12948a33e 100644 --- a/ts/types/Util.ts +++ b/ts/types/Util.ts @@ -1,11 +1,9 @@ -import { LocalizerKeys } from './LocalizerKeys'; - export type RenderTextCallbackType = (options: { text: string; key: number; isGroup: boolean; }) => JSX.Element; -export type LocalizerType = (key: LocalizerKeys, values?: Array) => string; +export type LocalizerType = typeof window.i18n; export type Noop = () => void; From 285b81db40b5a2d350b38ccb7833064c7ed27af5 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 14:43:51 +1100 Subject: [PATCH 19/93] feat: add concurrently and nodemon and locale building script --- package.json | 8 ++- yarn.lock | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index be8c27d80e..88ba182116 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,11 @@ "scripts": { "start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod$MULTI electron .", "start-dev": "cross-env NODE_ENV=development NODE_APP_INSTANCE=devprod$MULTI electron .", - "build-everything": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && tsc && yarn build:workers", - "build-everything:watch": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && yarn build:workers && tsc -w", + "build-everything": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && build:locales && tsc && yarn build:workers", + "build-everything:watch": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass &&build:locales && yarn build:workers && concurrently \"tsc -w\" \"yarn build-watch:locales\"", "build:workers": "yarn worker:utils && yarn worker:libsession", + "build:locales": "python3 ./tools/localization/generateLocales.py --print-problems --error-on-problems --error-old-dynamic-variables", + "build-watch:locales": "nodemon --watch _locales/en/messages.json --exec \"yarn build:locales\"", "watch": "yarn clean && yarn protobuf && yarn update-git-info && yarn build-everything:watch", "protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/compiled.js protos/*.proto && pbts --out ts/protobuf/compiled.d.ts ts/protobuf/compiled.js --force-long", "sass": "rimraf 'stylesheets/dist/' && webpack --config=./sass.config.js", @@ -173,6 +175,7 @@ "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-bytes": "^0.1.2", + "concurrently": "^8.2.2", "cross-env": "^6.0.3", "css-loader": "^6.7.2", "dmg-builder": "23.6.0", @@ -195,6 +198,7 @@ "mini-css-extract-plugin": "^2.7.5", "mocha": "10.0.0", "node-loader": "^2.0.0", + "nodemon": "^3.1.0", "patch-package": "^6.4.7", "postinstall-prepare": "^1.0.1", "prettier": "3.2.5", diff --git a/yarn.lock b/yarn.lock index 6ea5b2a819..11fc159fb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,6 +121,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.21.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1438,6 +1445,11 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + abort-controller@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2288,6 +2300,21 @@ chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0": optionalDependencies: fsevents "~2.3.2" +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -2500,6 +2527,21 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concurrently@^8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-8.2.2.tgz#353141985c198cfa5e4a3ef90082c336b5851784" + integrity sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg== + dependencies: + chalk "^4.1.2" + date-fns "^2.30.0" + lodash "^4.17.21" + rxjs "^7.8.1" + shell-quote "^1.8.1" + spawn-command "0.0.2" + supports-color "^8.1.1" + tree-kill "^1.2.2" + yargs "^17.7.2" + config@1.28.1: version "1.28.1" resolved "https://registry.yarnpkg.com/config/-/config-1.28.1.tgz#7625d2a1e4c90f131d8a73347982d93c3873282d" @@ -2708,6 +2750,13 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +date-fns@^2.30.0: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + date-fns@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed" @@ -4418,6 +4467,11 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -5751,6 +5805,29 @@ node-releases@^2.0.13: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +nodemon@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.0.tgz#ff7394f2450eb6a5e96fe4180acd5176b29799c9" + integrity sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -6307,6 +6384,11 @@ psl@^1.1.33, psl@^1.1.7: resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -6888,6 +6970,13 @@ run-script-os@^1.1.6: resolved "https://registry.yarnpkg.com/run-script-os/-/run-script-os-1.1.6.tgz#8b0177fb1b54c99a670f95c7fdc54f18b9c72347" integrity sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw== +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" @@ -7047,6 +7136,13 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.5.3: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -7136,6 +7232,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-quote@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -7150,6 +7251,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + sinon@9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d" @@ -7223,6 +7331,11 @@ sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +spawn-command@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" + integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== + spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" @@ -7473,7 +7586,7 @@ sumchecker@^3.0.1: dependencies: debug "^4.1.0" -supports-color@8.1.1, supports-color@^8.0.0: +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -7625,6 +7738,13 @@ toggle-selection@^1.0.6: resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" @@ -7647,6 +7767,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" @@ -7842,6 +7967,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + underscore@>=1.8.3, underscore@~1.13.2: version "1.13.6" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" @@ -8316,7 +8446,7 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0, yargs@^17.0.1, yargs@^17.6.0: +yargs@^17.0.0, yargs@^17.0.1, yargs@^17.6.0, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From f935485ab9775918efcc14e56b78e3556fac235a Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 14:44:27 +1100 Subject: [PATCH 20/93] fix: add ignores for the generated locale type file --- .eslintignore | 2 ++ .gitignore | 1 + .prettierignore | 2 ++ 3 files changed, 5 insertions(+) diff --git a/.eslintignore b/.eslintignore index cd371d9aef..c72fa9587e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,3 +15,5 @@ stylesheets/dist/ compiled.d.ts .eslintrc.js playwright.config.js + +ts/localization/locales.ts diff --git a/.gitignore b/.gitignore index a57cb1a51b..965af405c6 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ stylesheets/dist/ *.worker.js.LICENSE.txt ts/webworker/workers/node/**/*.node +ts/localization/locales.ts diff --git a/.prettierignore b/.prettierignore index 6a865d97f7..a5a365d923 100644 --- a/.prettierignore +++ b/.prettierignore @@ -28,3 +28,5 @@ release/** .nyc_output/ coverage/ stylesheets/dist/** + +ts/localization/locales.ts From d557b8ce68bcb86f4b03eb1e029ed3ea39b88e0e Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 15:25:58 +1100 Subject: [PATCH 21/93] fix: rename SessionHtmlRenderer as prop to tag to work with styled components --- ts/components/basic/I18n.tsx | 2 +- ts/components/basic/SessionHTMLRenderer.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/components/basic/I18n.tsx b/ts/components/basic/I18n.tsx index c248a41ff7..5b29dcb710 100644 --- a/ts/components/basic/I18n.tsx +++ b/ts/components/basic/I18n.tsx @@ -42,7 +42,7 @@ export const I18n = (props: I18nProps) => { /** If the string contains a relevant formatting tag, render it as HTML */ if (i18nString.match(formattingTagRegex)) { - return ; + return ; } const Comp = props.as ?? React.Fragment; diff --git a/ts/components/basic/SessionHTMLRenderer.tsx b/ts/components/basic/SessionHTMLRenderer.tsx index 83c0c46ea6..2a057a48aa 100644 --- a/ts/components/basic/SessionHTMLRenderer.tsx +++ b/ts/components/basic/SessionHTMLRenderer.tsx @@ -3,18 +3,18 @@ import React from 'react'; type ReceivedProps = { html: string; - as?: React.ElementType; + tag?: React.ElementType; key?: any; className?: string; }; -export const SessionHtmlRenderer = ({ as = 'div', key, html, className }: ReceivedProps) => { +export const SessionHtmlRenderer = ({ tag = 'div', key, html, className }: ReceivedProps) => { const clean = DOMPurify.sanitize(html, { USE_PROFILES: { html: true }, FORBID_ATTR: ['script'], }); - return React.createElement(as, { + return React.createElement(tag, { key, className, From bfee33d4425a01ee6a5fff747646bc9da150c417 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 28 Mar 2024 15:31:26 +1100 Subject: [PATCH 22/93] feat: create localized python strings scripts and utility functions --- tools/.gitignore | 4 + tools/README.md | 80 ++++- tools/localization/crowdInPostImport.sh | 23 ++ tools/localization/dynamicVariables.py | 215 ++++++++++++ tools/localization/findString.py | 73 ++++ tools/localization/generateLocales.py | 128 +++++++ .../generateLocalizedStringsAnalysis.py | 321 ++++++++++++++++++ tools/localization/localeTypes.py | 72 ++++ tools/localization/regex.py | 16 + tools/unusedLocalizedString.py | 55 --- tools/updateI18nKeysType.py | 31 -- tools/util/fileUtils.py | 80 +++++ tools/util/listUtils.py | 40 +++ tools/util/logger.py | 39 +++ tools/util/print.py | 5 + tools/util/sortJson.py | 38 +++ tools/util/time.py | 24 ++ 17 files changed, 1140 insertions(+), 104 deletions(-) create mode 100644 tools/.gitignore create mode 100644 tools/localization/crowdInPostImport.sh create mode 100644 tools/localization/dynamicVariables.py create mode 100644 tools/localization/findString.py create mode 100755 tools/localization/generateLocales.py create mode 100755 tools/localization/generateLocalizedStringsAnalysis.py create mode 100644 tools/localization/localeTypes.py create mode 100644 tools/localization/regex.py delete mode 100755 tools/unusedLocalizedString.py delete mode 100755 tools/updateI18nKeysType.py create mode 100644 tools/util/fileUtils.py create mode 100644 tools/util/listUtils.py create mode 100644 tools/util/logger.py create mode 100644 tools/util/print.py create mode 100644 tools/util/sortJson.py create mode 100644 tools/util/time.py diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000000..0c446df08f --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +/localization/analysis +/localization/output +/localization/input diff --git a/tools/README.md b/tools/README.md index 63a0c1e3d1..ff7aac3619 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,36 +1,80 @@ -**Those tools can be used to keep in sync our locale in the app between different language and with android translations** +# Tools +## Using the Python scripts +The Python scripts are located in the `tools` directory. To run a script, use the following command: -## Step 1: Find unused key locales in EN +```bash +python3 ./tools/