From 8bac803deab65f4cf7e1d2c6e791a4e96fee0a56 Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:21:57 +0100 Subject: [PATCH] Localization: Migrate more components (#5484) --- dev/generateInitialLangFallback.ts | 6 +- src/api/gramjs/methods/auth.ts | 41 ++++++--- src/api/gramjs/methods/settings.ts | 42 +++++---- src/api/types/updates.ts | 3 +- src/assets/localization/fallback.strings | 89 ++++++++++++++----- src/assets/localization/initialKeys.ts | 14 ++- src/assets/localization/initialStrings.ts | 22 ++++- src/components/auth/AuthCode.tsx | 29 +++--- src/components/auth/AuthPassword.tsx | 20 ++--- src/components/auth/AuthPhoneNumber.tsx | 34 +++---- src/components/auth/AuthQrCode.tsx | 24 +++-- src/components/auth/AuthRegister.tsx | 26 +++--- src/components/auth/CountryCodeInput.tsx | 8 +- .../left/settings/SettingsDataStorage.tsx | 18 ++-- .../left/settings/SettingsGeneral.tsx | 42 ++++----- .../left/settings/SettingsHeader.tsx | 2 +- src/components/left/settings/SettingsMain.tsx | 40 ++++----- .../left/settings/SettingsNotifications.tsx | 38 ++++---- .../left/settings/SettingsPerformance.tsx | 74 ++++++++------- src/config.ts | 1 - src/global/actions/api/initial.ts | 12 +-- src/global/actions/apiUpdaters/initial.ts | 3 +- src/global/actions/ui/initial.ts | 4 +- src/global/types/actions.ts | 2 +- src/global/types/globalState.ts | 3 +- src/hooks/useLangString.ts | 30 +++++++ src/hooks/useOldLangString.ts | 24 ----- src/types/language.d.ts | 58 +++++++++++- src/util/data/readStrings.ts | 2 +- src/util/fallbackLangPackInitial.ts | 33 ------- src/util/localization/index.ts | 7 ++ src/util/localization/types.ts | 4 + src/util/oldLangProvider.ts | 44 +-------- 33 files changed, 445 insertions(+), 354 deletions(-) create mode 100644 src/hooks/useLangString.ts delete mode 100644 src/hooks/useOldLangString.ts delete mode 100644 src/util/fallbackLangPackInitial.ts diff --git a/dev/generateInitialLangFallback.ts b/dev/generateInitialLangFallback.ts index 0839a4b47..1121f4950 100644 --- a/dev/generateInitialLangFallback.ts +++ b/dev/generateInitialLangFallback.ts @@ -5,14 +5,16 @@ import readFallbackStrings from '../src/util/data/readFallbackStrings'; import { pick } from '../src/util/iteratees'; const HEADER = `/* eslint-disable */ -// This file is generated by dev/generateInitialLangFallback.ts. Do not edit it manually.\n`; +// This file is generated by dev/generateInitialLangFallback.ts. Do not edit it manually.\n +import type { LangPackStringValue } from '../../api/types'; +import type { LangKey } from '../../types/language';\n`; async function main() { const data = await readFallbackStrings(true); const selectedKeys = pick(data.langPack.strings, initialKeys); const json = JSON.stringify(selectedKeys, undefined, 2); - const text = `${HEADER}\nexport default ${json} as Record;\n`; + const text = `${HEADER}\nexport default ${json} as Record;\n`; writeFileSync('./src/assets/localization/initialStrings.ts', text, 'utf8'); } diff --git a/src/api/gramjs/methods/auth.ts b/src/api/gramjs/methods/auth.ts index 57e1430e8..923f9e773 100644 --- a/src/api/gramjs/methods/auth.ts +++ b/src/api/gramjs/methods/auth.ts @@ -1,5 +1,7 @@ import { FloodWaitError, RPCError } from '../../../lib/gramjs/errors'; +import type { RegularLangKey } from '../../../types/language'; +import type { RegularLangFnParameters } from '../../../util/localization'; import type { ApiUpdateAuthorizationState, ApiUpdateAuthorizationStateType, @@ -10,12 +12,12 @@ import type { import { DEBUG } from '../../../config'; import { sendApiUpdate } from '../updates/apiUpdateEmitter'; -const ApiErrors: { [k: string]: string } = { - PHONE_NUMBER_INVALID: 'Invalid phone number.', - PHONE_CODE_INVALID: 'Invalid code.', - PASSWORD_HASH_INVALID: 'Incorrect password.', - PHONE_PASSWORD_FLOOD: 'Limit exceeded. Please try again later.', - PHONE_NUMBER_BANNED: 'This phone number is banned.', +const ApiErrors: Record = { + PHONE_NUMBER_INVALID: 'ErrorPhoneNumberInvalid', + PHONE_CODE_INVALID: 'ErrorCodeInvalid', + PASSWORD_HASH_INVALID: 'ErrorIncorrectPassword', + PHONE_PASSWORD_FLOOD: 'ErrorPasswordFlood', + PHONE_NUMBER_BANNED: 'ErrorPhoneBanned', }; const authController: { @@ -85,19 +87,30 @@ export function onRequestQrCode(qrCode: { token: Buffer; expires: number }) { } export function onAuthError(err: Error) { - let message: string; + let messageKey: RegularLangFnParameters | undefined; if (err instanceof FloodWaitError) { const hours = Math.ceil(Number(err.seconds) / 60 / 60); - message = `Too many attempts. Try again in ${hours > 1 ? `${hours} hours` : 'an hour'}`; + messageKey = { + key: 'ErrorFlood', + variables: { hour: hours }, + options: { pluralValue: hours }, + }; } else if (err instanceof RPCError) { - message = ApiErrors[err.errorMessage]; - } else { - message = err.message; + messageKey = { + key: ApiErrors[err.errorMessage], + }; + } else if (err.message) { + messageKey = { + key: 'ErrorUnexpectedMessage', + variables: { error: err.message }, + }; } - if (!message) { - message = 'Unexpected Error'; + if (!messageKey) { + messageKey = { + key: 'ErrorUnexpected', + }; if (DEBUG) { // eslint-disable-next-line no-console @@ -107,7 +120,7 @@ export function onAuthError(err: Error) { sendApiUpdate({ '@type': 'updateAuthorizationError', - message, + errorKey: messageKey, }); } diff --git a/src/api/gramjs/methods/settings.ts b/src/api/gramjs/methods/settings.ts index f4f215f13..22d349806 100644 --- a/src/api/gramjs/methods/settings.ts +++ b/src/api/gramjs/methods/settings.ts @@ -17,8 +17,8 @@ import type { import { ACCEPTABLE_USERNAME_ERRORS, BLOCKED_LIST_LIMIT, + LANG_PACK, MAX_INT_32, - OLD_DEFAULT_LANG_PACK, } from '../../../config'; import { buildCollectionByKey } from '../../../util/iteratees'; import { getServerTime } from '../../../util/serverTime'; @@ -36,7 +36,6 @@ import { buildApiWebSession, buildLangStrings, oldBuildLangPack, - oldBuildLangPackString, } from '../apiBuilders/misc'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { @@ -468,7 +467,7 @@ export async function fetchLangDifference({ export async function fetchLanguages(): Promise { const result = await invokeRequest(new GramJs.langpack.GetLanguages({ - langPack: OLD_DEFAULT_LANG_PACK, + langPack: LANG_PACK, })); if (!result) { return undefined; @@ -495,6 +494,27 @@ export async function fetchLanguage({ return buildApiLanguage(result); } +export async function fetchLangStrings({ + langPack, + langCode, + keys, +}: { + langPack: string; + langCode: string; + keys: string[]; +}) { + const result = await invokeRequest(new GramJs.langpack.GetStrings({ + langPack, + langCode, + keys, + })); + if (!result) { + return undefined; + } + + return buildLangStrings(result); +} + export async function oldFetchLangPack({ sourceLangPacks, langCode }: { sourceLangPacks: typeof LANG_PACKS; langCode: string; @@ -514,22 +534,6 @@ export async function oldFetchLangPack({ sourceLangPacks, langCode }: { return { langPack: Object.assign({}, ...collections.reverse()) as typeof collections[0] }; } -export async function oldFetchLangStrings({ langPack, langCode, keys }: { - langPack: string; langCode: string; keys: string[]; -}) { - const result = await invokeRequest(new GramJs.langpack.GetStrings({ - langPack, - langCode: BETA_LANG_CODES.includes(langCode) ? `${langCode}-raw` : langCode, - keys, - })); - - if (!result) { - return undefined; - } - - return result.map(oldBuildLangPackString); -} - export async function fetchPrivacySettings(privacyKey: ApiPrivacyKey) { const key = buildInputPrivacyKey(privacyKey); const result = await invokeRequest(new GramJs.account.GetPrivacy({ key })); diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index d223f0a27..bba5a039a 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -7,6 +7,7 @@ import type { VideoState, } from '../../lib/secret-sauce'; import type { ThreadId } from '../../types'; +import type { RegularLangFnParameters } from '../../util/localization'; import type { ApiBotMenuButton } from './bots'; import type { ApiGroupCall, ApiPhoneCall, @@ -84,7 +85,7 @@ export type ApiUpdateSession = { export type ApiUpdateAuthorizationError = { '@type': 'updateAuthorizationError'; - message: string; + errorKey: RegularLangFnParameters; }; export type ApiUpdateConnectionState = { diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 529a9b899..1e8d78473 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -162,7 +162,7 @@ "LoginQRLogin" = "Log in by QR Code"; "LoginQRTitle" = "Log in to Telegram by QR Code"; "LoginQRHelp1" = "Open Telegram on your phone"; -"LoginQR2Help2" = "Go to **Settings** > **Devices** > **Link Desktop Device**"; +"LoginQRHelp2" = "Go to **Settings** > **Devices** > **Link Desktop Device**"; "LoginQRHelp3" = "Point your phone at this screen to confirm login"; "LoginQRCancel" = "Log in by phone Number"; "YourName" = "Your Name"; @@ -394,6 +394,8 @@ "AuthSessionsViewBrowser" = "Browser"; "AuthSessionsViewLocationInfo" = "This location estimate is based on the IP address and may not always be accurate."; "AuthSessionsLogOutApplications" = "Disconnect All Websites"; +"AuthKeepSignedIn" = "Keep me signed in"; +"AuthTitle" = "Telegram"; "ClearOtherWebSessionsHelp" = "You can log in on websites that support signing in with Telegram."; "AreYouSureWebSessions" = "Are you sure you want to disconnect all websites where you logged in with Telegram?"; "AutodownloadSizeLimitUpTo" = "up to {limit}"; @@ -421,10 +423,17 @@ "SettingsSendEnter" = "Send with Enter"; "SettingsSendCmdenter" = "Send with Cmd+Enter"; "SettingsSendCtrlenter" = "Send with Ctrl+Enter"; +"SettingsSendEnterDescription" = "New line by Shift + Enter"; +"SettingsSendPlusEnterDescription" = "New line by Enter"; +"SettingsTimeFormat" = "Time Format"; +"SettingsTimeFormat12" = "12-hour"; +"SettingsTimeFormat24" = "24-hour"; +"SettingsKeyboard" = "Keyboard"; +"SettingsTray" = "Show Icon in Menu Bar"; +"SettingsOfflineNotificationUnsupported" = "Not supported"; "TextSize" = "Message Font Size"; "ChatBackground" = "Chat Wallpaper"; "Theme" = "Theme"; -"VoiceOverKeyboard" = "Keyboard"; "AccDescrStickers" = "Stickers"; "DoubleTapSetting" = "Quick Reaction"; "SuggestStickers" = "Sticker suggestions by emoji"; @@ -625,10 +634,49 @@ "PollsSolutionTitle" = "Explanation"; "CreatePollExplanationInfo" = "Users will see this comment after choosing a wrong answer, good for educational purposes."; "VoipGroupPersonalAccount" = "personal account"; +"MenuStickers" = "Stickers and Emoji"; +"MenuAnimations" = "Animations and Performance"; +"MenuStars" = "My Stars"; +"MenuSendGift" = "Send a Gift"; +"MenuTelegramFaq" = "Telegram FAQ"; +"MenuPrivacyPolicy" = "Privacy Policy"; +"MenuAskText" = "Please note that Telegram Support is done by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions."; +"SettingsPerformanceSliderTitle" = "Animation Level"; +"SettingsPerformanceSliderSubtitle" = "Choose the desired animations amount."; +"SettingsPerformanceSliderLow" = "Power Saving"; +"SettingsPerformanceSliderMedium" = "Nice and Fast"; +"SettingsPerformanceSliderCustom" = "Custom"; +"SettingsPerformanceSliderHigh" = "Lots of Stuff"; +"SettingsPerformanceInterfaceAnimations" = "Interface Animations"; +"SettingsPerformanceStickers" = "Stickers and Emoji"; +"SettingsPerformanceMediaAutoplay" = "Media Autoplay"; +"SettingsPerformancePageTransitions" = "Page Transitions"; +"SettingsPerformanceSending" = "Message Sending Animation"; +"SettingsPerformanceMediaViewer" = "Media Viewer Animations"; +"SettingsPerformanceComposer" = "Message Composer Animations"; +"SettingsPerformanceContextAnimation" = "Context Menu Animation"; +"SettingsPerformanceContextBlur" = "Context Menu Blur"; +"SettingsPerformanceRightColumn" = "Right Column Animation"; +"SettingsPerformanceThanos" = "Dust-effect deletion"; +"SettingsPerformanceAnimatedEmoji" = "Allow Animated Emoji"; +"SettingsPerformanceLoopStickers" = "Loop Animated Stickers"; +"SettingsPerformanceReactionEffects" = "Reaction Effects"; +"SettingsPerformanceStickerEffects" = "Sticker Effects"; +"SettingsPerformanceAutoplayGif" = "Autoplay GIFs"; +"SettingsPerformanceAutoplayVideo" = "Autoplay Videos"; "FavoriteStickers" = "Favorites"; "PremiumStickers" = "Premium Stickers"; "GroupStickers" = "Group Stickers"; "ErrorSendRestrictedStickersAll" = "Sorry, sending stickers is not allowed in this group."; +"ErrorPhoneNumberInvalid" = "Invalid phone number, please try again."; +"ErrorCodeInvalid" = "Invalid code, please try again."; +"ErrorIncorrectPassword" = "Invalid password, please try again."; +"ErrorPasswordFlood" = "Too many attempts, please try again later."; +"ErrorPhoneBanned" = "This phone number is banned."; +"ErrorFlood_one" = "Too many attempts, please try again in {hour} hour"; +"ErrorFlood_other" = "Too many attempts, please try again in {hour} hours"; +"ErrorUnexpected" = "Unexpected error"; +"ErrorUnexpectedMessage" = "Unexpected error: {error}"; "NoStickers" = "No stickers yet"; "ClearRecentEmoji" = "Clear recent emoji?"; "TextFormatAddLinkTitle" = "Add Link"; @@ -1343,8 +1391,8 @@ "GiftInfoConvertTitle" = "Convert Gift to Stars"; "GiftInfoConvertDescription1" = "Do you want to convert this gift from **{user}** to **{amount}**?"; "GiftInfoConvertDescription2" = "This action cannot be undone. This will permanently destroy the gift."; -"GiftInfoConvertDescriptionPeriod_one" = "Conversion is available for the next **{count} days**." -"GiftInfoConvertDescriptionPeriod_other" = "Conversion is available for the next **{count} days**." +"GiftInfoConvertDescriptionPeriod_one" = "Conversion is available for the next **{count} days**."; +"GiftInfoConvertDescriptionPeriod_other" = "Conversion is available for the next **{count} days**."; "GiftInfoSaved" = "This gift is visible on your profile. {link}"; "GiftInfoSavedView" = "View >"; "GiftInfoHidden" = "This gift is hidden. Only you can see it."; @@ -1363,10 +1411,10 @@ "GiftAttributeModel" = "Model"; "GiftAttributeBackdrop" = "Backdrop"; "GiftAttributeSymbol" = "Symbol"; -"GiftInfoOriginalInfo" = "Gifted to {user} on {date}." -"GiftInfoOriginalInfoSender" = "Gifted by {sender} to {user} on {date}." -"GiftInfoOriginalInfoText" = "Gifted to {user} on {date} with comment \"{text}\"." -"GiftInfoOriginalInfoTextSender" = "Gifted by {sender} to {user} on {date} with comment \"{text}\"." +"GiftInfoOriginalInfo" = "Gifted to {user} on {date}."; +"GiftInfoOriginalInfoSender" = "Gifted by {sender} to {user} on {date}."; +"GiftInfoOriginalInfoText" = "Gifted to {user} on {date} with comment \"{text}\"."; +"GiftInfoOriginalInfoTextSender" = "Gifted by {sender} to {user} on {date} with comment \"{text}\"."; "GiftInfoStatus" = "Status"; "GiftInfoStatusNonUnique" = "Non-Unique"; "GiftInfoViewUpgraded" = "View Upgraded Gift"; @@ -1399,7 +1447,7 @@ "ReceivedGift" = "Received Gift"; "SentGift" = "Sent Gift"; "StarGiftInfoDescriptionInbound" = "You can keep this gift in your Profile or convert it to {count} Stars. {link}"; -"StarGiftInfoDescriptionOutgoing" = "{user} can keep this gift in Profile or convert it to {count} Stars. {link}" +"StarGiftInfoDescriptionOutgoing" = "{user} can keep this gift in Profile or convert it to {count} Stars. {link}"; "StarGiftInfoLinkCaption" = "More About Stars >"; "StarGiftDisplayOnMyPage" = "Display on on my page"; "StarGiftConvertTo" = "Convert to"; @@ -1435,12 +1483,12 @@ "BotDownloadFileDescription" = "**{bot}** suggests you to download **{filename}**"; "BotDownloadFileButton" = "Download"; "PrivacyGifts" = "Gifts"; -"PrivacyGiftsTitle" = "Who can display gifts on my profile?" -"PrivacyGiftsInfo" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile." -"PrivacyValueBots" = "Mini Apps" -"CustomShareGiftsInfo" = "You can add users or entire groups as exceptions that will override the settings above." -"StarsSubscribeBotText_one" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** star per month?" -"StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?" +"PrivacyGiftsTitle" = "Who can display gifts on my profile?"; +"PrivacyGiftsInfo" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile."; +"PrivacyValueBots" = "Mini Apps"; +"CustomShareGiftsInfo" = "You can add users or entire groups as exceptions that will override the settings above."; +"StarsSubscribeBotText_one" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** star per month?"; +"StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?"; "StarsSubscribeBotButtonMonth" = "Subscribe for {amount} / month"; "AllChatsSearchContext" = "All Chats"; "PrivateChatsSearchContext" = "Private Chats"; @@ -1450,9 +1498,9 @@ "FolderLinkTitleDescription" = "Anyone with this link can add {folder} folder and {chats} selected below."; "FolderLinkTitleDescriptionChats_one" = "the chat"; "FolderLinkTitleDescriptionChats_other" = "the {count} chats"; -"FolderLinkSubtitleNew" = "Do you want to add a new chat folder and join its groups and channels?" -"FolderLinkSubtitleAlready" = "You have already added this folder and its chats." -"FolderLinkSubtitleAdd" = "Do you want to add {chats} to the folder **{title}**?" +"FolderLinkSubtitleNew" = "Do you want to add a new chat folder and join its groups and channels?"; +"FolderLinkSubtitleAlready" = "You have already added this folder and its chats."; +"FolderLinkSubtitleAdd" = "Do you want to add {chats} to the folder **{title}**?"; "FolderLinkSubtitleAddCount_one" = "1 chat"; "FolderLinkSubtitleAddCount_other" = "{count} chats"; "FolderLinkAddFolder" = "Add Folder"; @@ -1496,7 +1544,7 @@ "ActionUnsupportedDescription" = "Please, use one of our apps to complete this action."; "LocationPermissionText" = "**{name}** requests access to set your **location**. You will be able to revoke this access in the profile page of **{name}**."; "UnlockMoreSimilarBots" = "Show More Apps"; -"MoreSimilarBotsText" = "Subscribe to **Telegram Premium** to unlock up to {count} similar apps." +"MoreSimilarBotsText" = "Subscribe to **Telegram Premium** to unlock up to {count} similar apps."; "GiftWasNotFound" = "Gift was not found"; "ViewButtonRequestJoin" = "REQUEST TO JOIN"; "ViewButtonMessage" = "VIEW MESSAGE"; @@ -1511,4 +1559,5 @@ "ViewButtonStory" = "VIEW STORY"; "ViewButtonBoost" = "BOOST"; "ViewButtonStickerset" = "VIEW STICKERS"; -"ViewButtonGiftUnique" = "VIEW COLLECTIBLE"; \ No newline at end of file +"ViewButtonGiftUnique" = "VIEW COLLECTIBLE"; +"AuthContinueOnThisLanguage" = "Continue in English"; diff --git a/src/assets/localization/initialKeys.ts b/src/assets/localization/initialKeys.ts index 40110a28c..9d17a382b 100644 --- a/src/assets/localization/initialKeys.ts +++ b/src/assets/localization/initialKeys.ts @@ -1,4 +1,6 @@ -const INITIAL_KEYS = [ +import type { LangKey } from '../../types/language'; + +const INITIAL_KEYS: LangKey[] = [ 'WrongNumber', 'SentAppCode', 'LoginJustSentSms', @@ -11,7 +13,7 @@ const INITIAL_KEYS = [ 'LoginQRTitle', 'LoginQRHelp1', 'LoginQRHelp2', - 'LoginQR2Help2', + 'LoginQRHelp2', 'LoginQRHelp3', 'LoginQRCancel', 'YourName', @@ -21,6 +23,14 @@ const INITIAL_KEYS = [ 'LoginSelectCountryTitle', 'CountryNone', 'PleaseEnterPassword', + 'ErrorPhoneNumberInvalid', + 'ErrorCodeInvalid', + 'ErrorIncorrectPassword', + 'ErrorPasswordFlood', + 'ErrorPhoneBanned', + 'ErrorFlood', + 'ErrorUnexpected', + 'ErrorUnexpectedMessage', ]; export default INITIAL_KEYS; diff --git a/src/assets/localization/initialStrings.ts b/src/assets/localization/initialStrings.ts index 26a1092b1..7c42cf753 100644 --- a/src/assets/localization/initialStrings.ts +++ b/src/assets/localization/initialStrings.ts @@ -1,19 +1,22 @@ /* eslint-disable */ // This file is generated by dev/generateInitialLangFallback.ts. Do not edit it manually. +import type { LangPackStringValue } from '../../api/types'; +import type { LangKey } from '../../types/language'; + export default { "WrongNumber": "Wrong number?", "SentAppCode": "We've sent the code to the **Telegram** app on your other device.", "LoginJustSentSms": "We've sent you a code via SMS. Please enter it above.", "LoginHeaderPassword": "Enter Password", "LoginEnterPasswordDescription": "You have Two-Step Verification enabled, so your account is protected with an additional password.", - "StartText": "Please confirm your country codenand enter your phone number.", + "StartText": "Please confirm your country code\nand enter your phone number.", "LoginPhonePlaceholder": "Your phone number", "LoginNext": "Next", "LoginQRLogin": "Log in by QR Code", "LoginQRTitle": "Log in to Telegram by QR Code", "LoginQRHelp1": "Open Telegram on your phone", - "LoginQR2Help2": "Go to **Settings** > **Devices** > **Link Desktop Device**", + "LoginQRHelp2": "Go to **Settings** > **Devices** > **Link Desktop Device**", "LoginQRHelp3": "Point your phone at this screen to confirm login", "LoginQRCancel": "Log in by phone Number", "YourName": "Your Name", @@ -22,5 +25,16 @@ export default { "LoginRegisterLastNamePlaceholder": "Last Name", "LoginSelectCountryTitle": "Country", "CountryNone": "Country not found", - "PleaseEnterPassword": "Enter your new password" -} as Record; + "PleaseEnterPassword": "Enter your new password", + "ErrorPhoneNumberInvalid": "Invalid phone number, please try again.", + "ErrorCodeInvalid": "Invalid code, please try again.", + "ErrorIncorrectPassword": "Invalid password, please try again.", + "ErrorPasswordFlood": "Too many attempts, please try again later.", + "ErrorPhoneBanned": "This phone number is banned.", + "ErrorFlood": { + "one": "Too many attempts, please try again in {hour} hour", + "other": "Too many attempts, please try again in {hour} hours" + }, + "ErrorUnexpected": "Unexpected error", + "ErrorUnexpectedMessage": "Unexpected error: {error}" +} as Record; diff --git a/src/components/auth/AuthCode.tsx b/src/components/auth/AuthCode.tsx index c4be26833..e8fc8cd2a 100644 --- a/src/components/auth/AuthCode.tsx +++ b/src/components/auth/AuthCode.tsx @@ -9,16 +9,16 @@ import type { GlobalState } from '../../global/types'; import { pick } from '../../util/iteratees'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; -import renderText from '../common/helpers/renderText'; import useHistoryBack from '../../hooks/useHistoryBack'; -import useOldLang from '../../hooks/useOldLang'; +import useLang from '../../hooks/useLang'; +import Icon from '../common/icons/Icon'; import TrackingMonkey from '../common/TrackingMonkey'; import InputText from '../ui/InputText'; import Loading from '../ui/Loading'; -type StateProps = Pick; +type StateProps = Pick; const CODE_LENGTH = 5; @@ -26,15 +26,15 @@ const AuthCode: FC = ({ authPhoneNumber, authIsCodeViaApp, authIsLoading, - authError, + authErrorKey, }) => { const { setAuthCode, returnToAuthPhoneNumber, - clearAuthError, + clearAuthErrorKey, } = getActions(); - const lang = useOldLang(); + const lang = useLang(); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); @@ -54,8 +54,8 @@ const AuthCode: FC = ({ }); const onCodeChange = useCallback((e: FormEvent) => { - if (authError) { - clearAuthError(); + if (authErrorKey) { + clearAuthErrorKey(); } const { currentTarget: target } = e; @@ -82,7 +82,7 @@ const AuthCode: FC = ({ if (target.value.length === CODE_LENGTH) { setAuthCode({ code: target.value }); } - }, [authError, clearAuthError, code, isTracking, setAuthCode]); + }, [authErrorKey, code, isTracking, setAuthCode]); function handleReturnToAuthPhoneNumber() { returnToAuthPhoneNumber(); @@ -107,11 +107,14 @@ const AuthCode: FC = ({ title={lang('WrongNumber')} aria-label={lang('WrongNumber')} > - +

- {renderText(lang(authIsCodeViaApp ? 'SentAppCode' : 'Login.JustSentSms'), ['simple_markdown'])} + {lang(authIsCodeViaApp ? 'SentAppCode' : 'LoginJustSentSms', undefined, { + withNodes: true, + withMarkdown: true, + })}

= ({ label={lang('Code')} onInput={onCodeChange} value={code} - error={authError && lang(authError)} + error={authErrorKey && lang.withRegular(authErrorKey)} autoComplete="off" inputMode="numeric" /> @@ -130,5 +133,5 @@ const AuthCode: FC = ({ }; export default memo(withGlobal( - (global): StateProps => pick(global, ['authPhoneNumber', 'authIsCodeViaApp', 'authIsLoading', 'authError']), + (global): StateProps => pick(global, ['authPhoneNumber', 'authIsCodeViaApp', 'authIsLoading', 'authErrorKey']), )(AuthCode)); diff --git a/src/components/auth/AuthPassword.tsx b/src/components/auth/AuthPassword.tsx index 7a4f6a32e..ebe0f4751 100644 --- a/src/components/auth/AuthPassword.tsx +++ b/src/components/auth/AuthPassword.tsx @@ -6,19 +6,19 @@ import type { GlobalState } from '../../global/types'; import { pick } from '../../util/iteratees'; -import useOldLang from '../../hooks/useOldLang'; +import useLang from '../../hooks/useLang'; import PasswordForm from '../common/PasswordForm'; import MonkeyPassword from '../common/PasswordMonkey'; -type StateProps = Pick; +type StateProps = Pick; const AuthPassword: FC = ({ - authIsLoading, authError, authHint, + authIsLoading, authErrorKey, authHint, }) => { - const { setAuthPassword, clearAuthError } = getActions(); + const { setAuthPassword, clearAuthErrorKey } = getActions(); - const lang = useOldLang(); + const lang = useLang(); const [showPassword, setShowPassword] = useState(false); const handleChangePasswordVisibility = useCallback((isVisible) => { @@ -33,11 +33,11 @@ const AuthPassword: FC = ({
-

{lang('Login.Header.Password')}

-

{lang('Login.EnterPasswordDescription')}

+

{lang('LoginHeaderPassword')}

+

{lang('LoginEnterPasswordDescription')}

= ({ }; export default memo(withGlobal( - (global): StateProps => pick(global, ['authIsLoading', 'authError', 'authHint']), + (global): StateProps => pick(global, ['authIsLoading', 'authErrorKey', 'authHint']), )(AuthPassword)); diff --git a/src/components/auth/AuthPhoneNumber.tsx b/src/components/auth/AuthPhoneNumber.tsx index 5327ec7e4..e50186dca 100644 --- a/src/components/auth/AuthPhoneNumber.tsx +++ b/src/components/auth/AuthPhoneNumber.tsx @@ -18,8 +18,8 @@ import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; import useFlag from '../../hooks/useFlag'; -import useOldLang from '../../hooks/useOldLang'; -import useOldLangString from '../../hooks/useOldLangString'; +import useLang from '../../hooks/useLang'; +import useLangString from '../../hooks/useLangString'; import Button from '../ui/Button'; import Checkbox from '../ui/Checkbox'; @@ -32,7 +32,7 @@ import monkeyPath from '../../assets/monkey.svg'; type StateProps = Pick & { language?: string; @@ -49,7 +49,7 @@ const AuthPhoneNumber: FC = ({ authPhoneNumber, authIsLoading, authIsLoadingQrCode, - authError, + authErrorKey, authRememberMe, authNearestCountry, phoneCodeList, @@ -60,18 +60,18 @@ const AuthPhoneNumber: FC = ({ setAuthRememberMe, loadNearestCountry, loadCountryList, - clearAuthError, + clearAuthErrorKey, goToAuthQrCode, setSettingOption, } = getActions(); - const lang = useOldLang(); + const lang = useLang(); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); const suggestedLanguage = getSuggestedLanguage(); const isConnected = connectionState === 'connectionStateReady'; - const continueText = useOldLangString(isConnected ? suggestedLanguage : undefined, 'ContinueOnThisLanguage', true); + const continueText = useLangString('AuthContinueOnThisLanguage', suggestedLanguage); const [country, setCountry] = useState(); const [phoneNumber, setPhoneNumber] = useState(); const [isTouched, setIsTouched] = useState(false); @@ -161,8 +161,8 @@ const AuthPhoneNumber: FC = ({ }, []); const handlePhoneNumberChange = useCallback((e: ChangeEvent) => { - if (authError) { - clearAuthError(); + if (authErrorKey) { + clearAuthErrorKey(); } // This is for further screens. We delay it until user input to speed up the initial loading. @@ -186,7 +186,7 @@ const AuthPhoneNumber: FC = ({ && value.length - fullNumber.length > 1 && !isJustPastedRef.current ); parseFullNumber(shouldFixSafariAutoComplete ? `${country!.countryCode} ${value}` : value); - }, [authError, clearAuthError, country, fullNumber, parseFullNumber]); + }, [authErrorKey, country, fullNumber, parseFullNumber]); const handleKeepSessionChange = useCallback((e: ChangeEvent) => { setAuthRememberMe(e.target.checked); @@ -214,7 +214,7 @@ const AuthPhoneNumber: FC = ({
{!isQrMounted &&
}
-

{lang('Login.QR.Title')}

+

{lang('LoginQRTitle')}

    -
  1. {lang('Login.QR.Help1')}
  2. -
  3. {renderText(lang('Login.QR2.Help2'), ['simple_markdown'])}
  4. -
  5. {lang('Login.QR.Help3')}
  6. +
  7. {lang('LoginQRHelp1')}
  8. +
  9. {lang('LoginQRHelp2', undefined, { withNodes: true, withMarkdown: true })}
  10. +
  11. {lang('LoginQRHelp3')}
{isAuthReady && ( - + )} {suggestedLanguage && suggestedLanguage !== language && continueText && ( diff --git a/src/components/auth/AuthRegister.tsx b/src/components/auth/AuthRegister.tsx index 111a7b02c..9ccb8b6c3 100644 --- a/src/components/auth/AuthRegister.tsx +++ b/src/components/auth/AuthRegister.tsx @@ -7,35 +7,35 @@ import type { GlobalState } from '../../global/types'; import { pick } from '../../util/iteratees'; -import useOldLang from '../../hooks/useOldLang'; +import useLang from '../../hooks/useLang'; import AvatarEditable from '../ui/AvatarEditable'; import Button from '../ui/Button'; import InputText from '../ui/InputText'; -type StateProps = Pick; +type StateProps = Pick; const AuthRegister: FC = ({ - authIsLoading, authError, + authIsLoading, authErrorKey, }) => { - const { signUp, clearAuthError, uploadProfilePhoto } = getActions(); + const { signUp, clearAuthErrorKey, uploadProfilePhoto } = getActions(); - const lang = useOldLang(); + const lang = useLang(); const [isButtonShown, setIsButtonShown] = useState(false); const [croppedFile, setCroppedFile] = useState(); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const handleFirstNameChange = useCallback((event: ChangeEvent) => { - if (authError) { - clearAuthError(); + if (authErrorKey) { + clearAuthErrorKey(); } const { target } = event; setFirstName(target.value); setIsButtonShown(target.value.length > 0); - }, [authError, clearAuthError]); + }, [authErrorKey]); const handleLastNameChange = useCallback((event: ChangeEvent) => { const { target } = event; @@ -59,18 +59,18 @@ const AuthRegister: FC = ({

{lang('YourName')}

-

{lang('Login.Register.Desc')}

+

{lang('LoginRegisterDesc')}

= ({ }; export default memo(withGlobal( - (global): StateProps => pick(global, ['authIsLoading', 'authError']), + (global): StateProps => pick(global, ['authIsLoading', 'authErrorKey']), )(AuthRegister)); diff --git a/src/components/auth/CountryCodeInput.tsx b/src/components/auth/CountryCodeInput.tsx index 95279ee52..44e35bf97 100644 --- a/src/components/auth/CountryCodeInput.tsx +++ b/src/components/auth/CountryCodeInput.tsx @@ -12,7 +12,7 @@ import { isoToEmoji } from '../../util/emoji/emoji'; import { prepareSearchWordsForNeedle } from '../../util/searchWords'; import renderText from '../common/helpers/renderText'; -import useOldLang from '../../hooks/useOldLang'; +import useLang from '../../hooks/useLang'; import useSyncEffect from '../../hooks/useSyncEffect'; import DropdownMenu from '../ui/DropdownMenu'; @@ -42,7 +42,7 @@ const CountryCodeInput: FC = ({ onChange, phoneCodeList, }) => { - const lang = useOldLang(); + const lang = useLang(); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); @@ -120,7 +120,7 @@ const CountryCodeInput: FC = ({ onInput={handleCodeInput} onKeyDown={handleInputKeyDown} /> - + {isLoading ? ( ) : ( @@ -154,7 +154,7 @@ const CountryCodeInput: FC = ({ className="no-results" disabled > - {lang('lng_country_none')} + {lang('CountryNone')} )} diff --git a/src/components/left/settings/SettingsDataStorage.tsx b/src/components/left/settings/SettingsDataStorage.tsx index 24e07dacc..0fe001a05 100644 --- a/src/components/left/settings/SettingsDataStorage.tsx +++ b/src/components/left/settings/SettingsDataStorage.tsx @@ -8,7 +8,7 @@ import { AUTODOWNLOAD_FILESIZE_MB_LIMITS } from '../../../config'; import { pick } from '../../../util/iteratees'; import useHistoryBack from '../../../hooks/useHistoryBack'; -import useOldLang from '../../../hooks/useOldLang'; +import useLang from '../../../hooks/useLang'; import Checkbox from '../../ui/Checkbox'; import RangeSlider from '../../ui/RangeSlider'; @@ -53,7 +53,7 @@ const SettingsDataStorage: FC = ({ }) => { const { setSettingOption } = getActions(); - const lang = useOldLang(); + const lang = useLang(); useHistoryBack({ isActive, @@ -61,7 +61,9 @@ const SettingsDataStorage: FC = ({ }); const renderFileSizeCallback = useCallback((value: number) => { - return lang('AutodownloadSizeLimitUpTo', lang('FileSize.MB', String(AUTODOWNLOAD_FILESIZE_MB_LIMITS[value]), 'i')); + return lang('AutodownloadSizeLimitUpTo', { + limit: lang('FileSizeMB', { count: AUTODOWNLOAD_FILESIZE_MB_LIMITS[value] }), + }); }, [lang]); const handleFileSizeChange = useCallback((value: number) => { @@ -98,26 +100,26 @@ const SettingsDataStorage: FC = ({

{title}

setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })} /> setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })} /> setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })} /> setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })} @@ -147,7 +149,7 @@ const SettingsDataStorage: FC = ({ canAutoLoadVideoInChannels, )} {renderAutoDownloadBlock( - 'Auto-download files', // Proper translation is not available yet + lang('AutoDownloadFilesTitle'), 'File', canAutoLoadFileFromContacts, canAutoLoadFileInPrivateChats, diff --git a/src/components/left/settings/SettingsGeneral.tsx b/src/components/left/settings/SettingsGeneral.tsx index 2ea1071ac..f36871054 100644 --- a/src/components/left/settings/SettingsGeneral.tsx +++ b/src/components/left/settings/SettingsGeneral.tsx @@ -17,7 +17,7 @@ import { import useAppLayout from '../../../hooks/useAppLayout'; import useHistoryBack from '../../../hooks/useHistoryBack'; -import useOldLang from '../../../hooks/useOldLang'; +import useLang from '../../../hooks/useLang'; import Checkbox from '../../ui/Checkbox'; import ListItem from '../../ui/ListItem'; @@ -41,14 +41,6 @@ type StateProps = shouldUseSystemTheme: boolean; }; -const TIME_FORMAT_OPTIONS: IRadioOption[] = [{ - label: '12-hour', - value: '12h', -}, { - label: '24-hour', - value: '24h', -}]; - const SettingsGeneral: FC = ({ isActive, onScreenSelect, @@ -63,28 +55,36 @@ const SettingsGeneral: FC = ({ setSettingOption, } = getActions(); - const lang = useOldLang(); + const lang = useLang(); const { isMobile } = useAppLayout(); const isMobileDevice = isMobile && (IS_IOS || IS_ANDROID); + const timeFormatOptions: IRadioOption[] = [{ + label: lang('SettingsTimeFormat12'), + value: '12h', + }, { + label: lang('SettingsTimeFormat24'), + value: '24h', + }]; + const appearanceThemeOptions: IRadioOption[] = [{ - label: lang('EmptyChat.Appearance.Light'), + label: lang('EmptyChatAppearanceLight'), value: 'light', }, { - label: lang('EmptyChat.Appearance.Dark'), + label: lang('EmptyChatAppearanceDark'), value: 'dark', }, { - label: lang('EmptyChat.Appearance.System'), + label: lang('EmptyChatAppearanceSystem'), value: 'auto', }]; const keyboardSendOptions = !isMobileDevice ? [ - { value: 'enter', label: lang('lng_settings_send_enter'), subLabel: 'New line by Shift + Enter' }, + { value: 'enter', label: lang('SettingsSendEnter'), subLabel: lang('SettingsSendEnterDescription') }, { value: 'ctrl-enter', - label: lang(IS_MAC_OS || IS_IOS ? 'lng_settings_send_cmdenter' : 'lng_settings_send_ctrlenter'), - subLabel: 'New line by Enter', + label: lang(IS_MAC_OS || IS_IOS ? 'SettingsSendCmdenter' : 'SettingsSendCtrlenter'), + subLabel: lang('SettingsSendPlusEnterDescription'), }, ] : undefined; @@ -134,7 +134,7 @@ const SettingsGeneral: FC = ({ return (
-

{lang('SETTINGS')}

+

{lang('Settings')}

= ({ {IS_ELECTRON && IS_WINDOWS && ( @@ -176,11 +176,11 @@ const SettingsGeneral: FC = ({

- Time Format + {lang('SettingsTimeFormat')}

@@ -188,7 +188,7 @@ const SettingsGeneral: FC = ({ {keyboardSendOptions && (
-

{lang('VoiceOver.Keyboard')}

+

{lang('SettingsKeyboard')}

= ({ return

{oldLang('NeverAllow')}

; case SettingsScreens.Performance: - return

{oldLang('Animations and Performance')}

; + return

{lang('MenuAnimations')}

; case SettingsScreens.ActiveSessions: return

{oldLang('SessionsTitle')}

; diff --git a/src/components/left/settings/SettingsMain.tsx b/src/components/left/settings/SettingsMain.tsx index e9c67f570..c07ff408c 100644 --- a/src/components/left/settings/SettingsMain.tsx +++ b/src/components/left/settings/SettingsMain.tsx @@ -16,7 +16,6 @@ import useFlag from '../../../hooks/useFlag'; import useHistoryBack from '../../../hooks/useHistoryBack'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; -import useOldLang from '../../../hooks/useOldLang'; import StarIcon from '../../common/icons/StarIcon'; import ChatExtra from '../../common/profile/ChatExtra'; @@ -62,7 +61,6 @@ const SettingsMain: FC = ({ const [isSupportDialogOpen, openSupportDialog, closeSupportDialog] = useFlag(false); const lang = useLang(); - const oldLang = useOldLang(); useEffect(() => { if (currentUserId) { @@ -104,7 +102,7 @@ const SettingsMain: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.General)} > - {oldLang('Telegram.GeneralSettingsViewController')} + {lang('TelegramGeneralSettingsViewController')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.Performance)} > - {oldLang('Animations and Performance')} + {lang('MenuAnimations')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.Notifications)} > - {oldLang('Notifications')} + {lang('Notifications')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.DataStorage)} > - {oldLang('DataSettings')} + {lang('DataSettings')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.Privacy)} > - {oldLang('PrivacySettings')} + {lang('PrivacySettings')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.Folders)} > - {oldLang('Filters')} + {lang('Filters')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.ActiveSessions)} > - {oldLang('SessionsTitle')} + {lang('SessionsTitle')} {sessionCount > 0 && ({sessionCount})} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.Language)} > - {oldLang('Language')} - {oldLang.langName} + {lang('Language')} + {lang.languageInfo.nativeName} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => onScreenSelect(SettingsScreens.Stickers)} > - {oldLang('StickersName')} + {lang('MenuStickers')}
@@ -181,7 +179,7 @@ const SettingsMain: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => openPremiumModal()} > - {oldLang('TelegramPremium')} + {lang('TelegramPremium')} )} {shouldDisplayStars && ( @@ -191,7 +189,7 @@ const SettingsMain: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => openStarsBalanceModal({})} > - {oldLang('MenuTelegramStars')} + {lang('MenuStars')} {Boolean(starsBalance) && ( {formatStarsAmount(lang, starsBalance)} @@ -206,7 +204,7 @@ const SettingsMain: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => openGiftRecipientPicker()} > - {oldLang('SendAGift')} + {lang('MenuSendGift')} )}
@@ -216,7 +214,7 @@ const SettingsMain: FC = ({ narrow onClick={openSupportDialog} > - {oldLang('AskAQuestion')} + {lang('AskAQuestion')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => openUrl({ url: FAQ_URL })} > - {oldLang('TelegramFaq')} + {lang('MenuTelegramFaq')} = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => openUrl({ url: PRIVACY_URL })} > - {oldLang('PrivacyPolicy')} + {lang('MenuPrivacyPolicy')}
diff --git a/src/components/left/settings/SettingsNotifications.tsx b/src/components/left/settings/SettingsNotifications.tsx index 101f6d7a7..804e28a55 100644 --- a/src/components/left/settings/SettingsNotifications.tsx +++ b/src/components/left/settings/SettingsNotifications.tsx @@ -10,7 +10,7 @@ import { } from '../../../util/notifications'; import useHistoryBack from '../../../hooks/useHistoryBack'; -import useOldLang from '../../../hooks/useOldLang'; +import useLang from '../../../hooks/useLang'; import useRunDebounced from '../../../hooks/useRunDebounced'; import Checkbox from '../../ui/Checkbox'; @@ -139,7 +139,7 @@ const SettingsNotifications: FC = ({ runDebounced(() => playNotifySound(undefined, volume)); }, [runDebounced, updateWebNotificationSettings]); - const lang = useOldLang(); + const lang = useLang(); useHistoryBack({ isActive, @@ -150,27 +150,27 @@ const SettingsNotifications: FC = ({

- Web notifications + {lang('NotificationsWeb')}

= ({ @@ -206,14 +206,14 @@ const SettingsNotifications: FC = ({ @@ -224,16 +224,14 @@ const SettingsNotifications: FC = ({ diff --git a/src/components/left/settings/SettingsPerformance.tsx b/src/components/left/settings/SettingsPerformance.tsx index 4a4993971..7c6d7dd18 100644 --- a/src/components/left/settings/SettingsPerformance.tsx +++ b/src/components/left/settings/SettingsPerformance.tsx @@ -4,6 +4,7 @@ import React, { import { getActions, withGlobal } from '../../../global'; import type { AnimationLevel, PerformanceType, PerformanceTypeKey } from '../../../types'; +import type { RegularLangKey } from '../../../types/language'; import { ANIMATION_LEVEL_CUSTOM, ANIMATION_LEVEL_MAX, ANIMATION_LEVEL_MED, ANIMATION_LEVEL_MIN, @@ -18,15 +19,15 @@ import { areDeepEqual } from '../../../util/areDeepEqual'; import { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../util/windowEnvironment'; import useHistoryBack from '../../../hooks/useHistoryBack'; -import useOldLang from '../../../hooks/useOldLang'; +import useLang from '../../../hooks/useLang'; import Checkbox from '../../ui/Checkbox'; import RangeSlider from '../../ui/RangeSlider'; -type PerformanceSection = [string, PerformanceOption[]]; +type PerformanceSection = [RegularLangKey, PerformanceOption[]]; type PerformanceOption = { key: PerformanceTypeKey; - label: string; + label: RegularLangKey; disabled?: boolean; }; @@ -39,38 +40,38 @@ type StateProps = { performanceSettings: PerformanceType; }; -const ANIMATION_LEVEL_OPTIONS = [ - 'Power Saving', - 'Nice and Fast', - 'Lots of Stuff', +const ANIMATION_LEVEL_OPTIONS: RegularLangKey[] = [ + 'SettingsPerformanceSliderLow', + 'SettingsPerformanceSliderMedium', + 'SettingsPerformanceSliderHigh', ]; -const ANIMATION_LEVEL_CUSTOM_OPTIONS = [ - 'Power Saving', - 'Custom', - 'Lots of Stuff', +const ANIMATION_LEVEL_CUSTOM_OPTIONS: RegularLangKey[] = [ + 'SettingsPerformanceSliderLow', + 'SettingsPerformanceSliderCustom', + 'SettingsPerformanceSliderHigh', ]; const PERFORMANCE_OPTIONS: PerformanceSection[] = [ - ['LiteMode.Key.animations.Title', [ - { key: 'pageTransitions', label: 'Page Transitions' }, - { key: 'messageSendingAnimations', label: 'Message Sending Animation' }, - { key: 'mediaViewerAnimations', label: 'Media Viewer Animations' }, - { key: 'messageComposerAnimations', label: 'Message Composer Animations' }, - { key: 'contextMenuAnimations', label: 'Context Menu Animation' }, - { key: 'contextMenuBlur', label: 'Context Menu Blur', disabled: !IS_BACKDROP_BLUR_SUPPORTED }, - { key: 'rightColumnAnimations', label: 'Right Column Animation' }, - { key: 'snapEffect', label: 'Dust-effect deletion' }, + ['SettingsPerformanceInterfaceAnimations', [ + { key: 'pageTransitions', label: 'SettingsPerformancePageTransitions' }, + { key: 'messageSendingAnimations', label: 'SettingsPerformanceSending' }, + { key: 'mediaViewerAnimations', label: 'SettingsPerformanceMediaViewer' }, + { key: 'messageComposerAnimations', label: 'SettingsPerformanceComposer' }, + { key: 'contextMenuAnimations', label: 'SettingsPerformanceContextAnimation' }, + { key: 'contextMenuBlur', label: 'SettingsPerformanceContextBlur', disabled: !IS_BACKDROP_BLUR_SUPPORTED }, + { key: 'rightColumnAnimations', label: 'SettingsPerformanceRightColumn' }, + { key: 'snapEffect', label: 'SettingsPerformanceThanos' }, ]], - ['Stickers and Emoji', [ - { key: 'animatedEmoji', label: 'Allow Animated Emoji' }, - { key: 'loopAnimatedStickers', label: 'Loop Animated Stickers' }, - { key: 'reactionEffects', label: 'Reaction Effects' }, - { key: 'stickerEffects', label: 'Full-Screen Sticker and Emoji Effects' }, + ['SettingsPerformanceStickers', [ + { key: 'animatedEmoji', label: 'SettingsPerformanceAnimatedEmoji' }, + { key: 'loopAnimatedStickers', label: 'SettingsPerformanceLoopStickers' }, + { key: 'reactionEffects', label: 'SettingsPerformanceReactionEffects' }, + { key: 'stickerEffects', label: 'SettingsPerformanceStickerEffects' }, ]], - ['AutoplayMedia', [ - { key: 'autoplayGifs', label: 'AutoplayGIF' }, - { key: 'autoplayVideos', label: 'AutoplayVideo' }, + ['SettingsPerformanceMediaAutoplay', [ + { key: 'autoplayGifs', label: 'SettingsPerformanceAutoplayGif' }, + { key: 'autoplayVideos', label: 'SettingsPerformanceAutoplayVideo' }, ]], ]; @@ -89,7 +90,7 @@ function SettingsPerformance({ onBack: onReset, }); - const lang = useOldLang(); + const lang = useLang(); const [sectionExpandedStates, setSectionExpandedStates] = useState>({}); const sectionCheckedStates = useMemo(() => { @@ -113,9 +114,14 @@ function SettingsPerformance({ return ANIMATION_LEVEL_CUSTOM; }, [performanceSettings]); - const animationLevelOptions = animationLevelState === ANIMATION_LEVEL_CUSTOM - ? ANIMATION_LEVEL_CUSTOM_OPTIONS - : ANIMATION_LEVEL_OPTIONS; + + const animationLevelOptions = useMemo(() => { + const options = animationLevelState === ANIMATION_LEVEL_CUSTOM + ? ANIMATION_LEVEL_CUSTOM_OPTIONS + : ANIMATION_LEVEL_OPTIONS; + + return options.map((option) => lang(option)); + }, [animationLevelState, lang]); const handleToggleSection = useCallback((e: React.MouseEvent, index?: string) => { e.preventDefault(); @@ -161,10 +167,10 @@ function SettingsPerformance({

- Animation Level + {lang('SettingsPerformanceSliderTitle')}

- Choose the desired animations amount. + {lang('SettingsPerformanceSliderSubtitle')}

return { ...global, authIsLoading: true, - authError: undefined, + authErrorKey: undefined, }; }); @@ -93,7 +93,7 @@ addActionHandler('setAuthPassword', (global, actions, payload): ActionReturnType return { ...global, authIsLoading: true, - authError: undefined, + authErrorKey: undefined, }; }); @@ -124,7 +124,7 @@ addActionHandler('signUp', (global, actions, payload): ActionReturnType => { return { ...global, authIsLoading: true, - authError: undefined, + authErrorKey: undefined, }; }); @@ -133,7 +133,7 @@ addActionHandler('returnToAuthPhoneNumber', (global): ActionReturnType => { return { ...global, - authError: undefined, + authErrorKey: undefined, }; }); @@ -143,7 +143,7 @@ addActionHandler('goToAuthQrCode', (global): ActionReturnType => { return { ...global, authIsLoadingQrCode: true, - authError: undefined, + authErrorKey: undefined, }; }); diff --git a/src/global/actions/apiUpdaters/initial.ts b/src/global/actions/apiUpdaters/initial.ts index 701a3bee1..d789c742f 100644 --- a/src/global/actions/apiUpdaters/initial.ts +++ b/src/global/actions/apiUpdaters/initial.ts @@ -180,10 +180,9 @@ function onUpdateAuthorizationState(global: T, update: Ap } function onUpdateAuthorizationError(global: T, update: ApiUpdateAuthorizationError) { - global = getGlobal(); global = { ...global, - authError: update.message, + authErrorKey: update.errorKey, }; setGlobal(global); } diff --git a/src/global/actions/ui/initial.ts b/src/global/actions/ui/initial.ts index c0819ea1a..8f035d43a 100644 --- a/src/global/actions/ui/initial.ts +++ b/src/global/actions/ui/initial.ts @@ -217,10 +217,10 @@ addActionHandler('setAuthRememberMe', (global, actions, payload): ActionReturnTy }; }); -addActionHandler('clearAuthError', (global): ActionReturnType => { +addActionHandler('clearAuthErrorKey', (global): ActionReturnType => { return { ...global, - authError: undefined, + authErrorKey: undefined, }; }); diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index 0833444be..42e6939be 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -116,7 +116,7 @@ export interface ActionPayloads { }; returnToAuthPhoneNumber: undefined; setAuthRememberMe: boolean; - clearAuthError: undefined; + clearAuthErrorKey: undefined; uploadProfilePhoto: { file: File; isFallback?: boolean; diff --git a/src/global/types/globalState.ts b/src/global/types/globalState.ts index 570c3a53c..2fe9a3774 100644 --- a/src/global/types/globalState.ts +++ b/src/global/types/globalState.ts @@ -68,6 +68,7 @@ import type { TopicsInfo, WebPageMediaSize, } from '../../types'; +import type { RegularLangFnParameters } from '../../util/localization'; import type { TabState } from './tabState'; export type GlobalState = { @@ -145,7 +146,7 @@ export type GlobalState = { authPhoneNumber?: string; authIsLoading?: boolean; authIsLoadingQrCode?: boolean; - authError?: string; + authErrorKey?: RegularLangFnParameters; authRememberMe?: boolean; authNearestCountry?: string; authIsCodeViaApp?: boolean; diff --git a/src/hooks/useLangString.ts b/src/hooks/useLangString.ts new file mode 100644 index 000000000..fe47c3078 --- /dev/null +++ b/src/hooks/useLangString.ts @@ -0,0 +1,30 @@ +import { useEffect, useState } from '../lib/teact/teact'; + +import type { RegularLangKey } from '../types/language'; + +import { LANG_PACK } from '../config'; +import { callApi } from '../api/gramjs'; +import useLastCallback from './useLastCallback'; + +export default function useLangString(key: RegularLangKey, langCode?: string) { + const [value, setValue] = useState(undefined); + + const fetchLangString = useLastCallback(async () => { + if (!langCode) return undefined; + + const result = await callApi('fetchLangStrings', { + langCode, + langPack: LANG_PACK, + keys: [key], + }); + const langString = result?.strings[0]; + if (!langString || typeof langString !== 'string') return undefined; + return langString; + }); + + useEffect(() => { + fetchLangString().then(setValue); + }, [key, langCode]); + + return value; +} diff --git a/src/hooks/useOldLangString.ts b/src/hooks/useOldLangString.ts deleted file mode 100644 index ed145e13c..000000000 --- a/src/hooks/useOldLangString.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as langProvider from '../util/oldLangProvider'; -import useAsync from './useAsync'; - -/** - * @deprecated Migrate to `useLang`, while using needed key inside initial fallback - */ -const useOldLangString = ( - langCode: string | undefined, - key: string, - shouldIgnoreSameValue = false, -): string | undefined => { - const defaultValue = shouldIgnoreSameValue ? undefined : key; - const { result } = useAsync(() => { - if (langCode) { - return langProvider.getTranslationForLangString(langCode, key); - } - - return Promise.resolve(); - }, [langCode, key], defaultValue); - - return result || defaultValue; -}; - -export default useOldLangString; diff --git a/src/types/language.d.ts b/src/types/language.d.ts index ddb05bce1..de2c5c560 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -142,7 +142,7 @@ export interface LangPair { 'LoginQRLogin': undefined; 'LoginQRTitle': undefined; 'LoginQRHelp1': undefined; - 'LoginQR2Help2': undefined; + 'LoginQRHelp2': undefined; 'LoginQRHelp3': undefined; 'LoginQRCancel': undefined; 'YourName': undefined; @@ -344,6 +344,8 @@ export interface LangPair { 'AuthSessionsViewBrowser': undefined; 'AuthSessionsViewLocationInfo': undefined; 'AuthSessionsLogOutApplications': undefined; + 'AuthKeepSignedIn': undefined; + 'AuthTitle': undefined; 'ClearOtherWebSessionsHelp': undefined; 'AreYouSureWebSessions': undefined; 'AutoDownloadMaxFileSize': undefined; @@ -369,10 +371,17 @@ export interface LangPair { 'SettingsSendEnter': undefined; 'SettingsSendCmdenter': undefined; 'SettingsSendCtrlenter': undefined; + 'SettingsSendEnterDescription': undefined; + 'SettingsSendPlusEnterDescription': undefined; + 'SettingsTimeFormat': undefined; + 'SettingsTimeFormat12': undefined; + 'SettingsTimeFormat24': undefined; + 'SettingsKeyboard': undefined; + 'SettingsTray': undefined; + 'SettingsOfflineNotificationUnsupported': undefined; 'TextSize': undefined; 'ChatBackground': undefined; 'Theme': undefined; - 'VoiceOverKeyboard': undefined; 'AccDescrStickers': undefined; 'DoubleTapSetting': undefined; 'SuggestStickers': undefined; @@ -544,10 +553,46 @@ export interface LangPair { 'PollsSolutionTitle': undefined; 'CreatePollExplanationInfo': undefined; 'VoipGroupPersonalAccount': undefined; + 'MenuStickers': undefined; + 'MenuAnimations': undefined; + 'MenuStars': undefined; + 'MenuSendGift': undefined; + 'MenuTelegramFaq': undefined; + 'MenuPrivacyPolicy': undefined; + 'MenuAskText': undefined; + 'SettingsPerformanceSliderTitle': undefined; + 'SettingsPerformanceSliderSubtitle': undefined; + 'SettingsPerformanceSliderLow': undefined; + 'SettingsPerformanceSliderMedium': undefined; + 'SettingsPerformanceSliderCustom': undefined; + 'SettingsPerformanceSliderHigh': undefined; + 'SettingsPerformanceInterfaceAnimations': undefined; + 'SettingsPerformanceStickers': undefined; + 'SettingsPerformanceMediaAutoplay': undefined; + 'SettingsPerformancePageTransitions': undefined; + 'SettingsPerformanceSending': undefined; + 'SettingsPerformanceMediaViewer': undefined; + 'SettingsPerformanceComposer': undefined; + 'SettingsPerformanceContextAnimation': undefined; + 'SettingsPerformanceContextBlur': undefined; + 'SettingsPerformanceRightColumn': undefined; + 'SettingsPerformanceThanos': undefined; + 'SettingsPerformanceAnimatedEmoji': undefined; + 'SettingsPerformanceLoopStickers': undefined; + 'SettingsPerformanceReactionEffects': undefined; + 'SettingsPerformanceStickerEffects': undefined; + 'SettingsPerformanceAutoplayGif': undefined; + 'SettingsPerformanceAutoplayVideo': undefined; 'FavoriteStickers': undefined; 'PremiumStickers': undefined; 'GroupStickers': undefined; 'ErrorSendRestrictedStickersAll': undefined; + 'ErrorPhoneNumberInvalid': undefined; + 'ErrorCodeInvalid': undefined; + 'ErrorIncorrectPassword': undefined; + 'ErrorPasswordFlood': undefined; + 'ErrorPhoneBanned': undefined; + 'ErrorUnexpected': undefined; 'NoStickers': undefined; 'ClearRecentEmoji': undefined; 'TextFormatAddLinkTitle': undefined; @@ -994,7 +1039,6 @@ export interface LangPair { 'SettingsAnimationsHigh': undefined; 'Settings12HourFormat': undefined; 'Settings24HourFormat': undefined; - 'SettingsSendEnterDescription': undefined; 'SettingsSendCtrlEnterDescription': undefined; 'AriaMoreButton': undefined; 'RecoveryEmailCode': undefined; @@ -1094,7 +1138,6 @@ export interface LangPair { 'HideCaption': undefined; 'ChangeRecipient': undefined; 'DragToSortAria': undefined; - 'SettingsTimeFormat': undefined; 'MenuReportBug': undefined; 'MenuBetaChangelog': undefined; 'MenuSwitchToK': undefined; @@ -1239,6 +1282,7 @@ export interface LangPair { 'ViewButtonBoost': undefined; 'ViewButtonStickerset': undefined; 'ViewButtonGiftUnique': undefined; + 'AuthContinueOnThisLanguage': undefined; } export interface LangPairWithVariables { @@ -1404,6 +1448,9 @@ export interface LangPairWithVariables { 'SlowModeHint': { 'time': V; }; + 'ErrorUnexpectedMessage': { + 'error': V; + }; 'EditedDate': { 'date': V; }; @@ -1761,6 +1808,9 @@ export interface LangPairPluralWithVariables { 'PreviewSenderSendFile': { 'count': V; }; + 'ErrorFlood': { + 'hour': V; + }; 'PinnedMessageTitle': { 'index': V; }; diff --git a/src/util/data/readStrings.ts b/src/util/data/readStrings.ts index ff83c2a42..0100dbc41 100644 --- a/src/util/data/readStrings.ts +++ b/src/util/data/readStrings.ts @@ -1,7 +1,7 @@ import { DEBUG } from '../../config'; export default function readStrings(data: string): Record { - const lines = data.split(/;?\r?\n/); + const lines = data.split(/;\r?\n?/); const result: Record = {}; for (const line of lines) { if (!line.startsWith('"')) continue; diff --git a/src/util/fallbackLangPackInitial.ts b/src/util/fallbackLangPackInitial.ts deleted file mode 100644 index df27df966..000000000 --- a/src/util/fallbackLangPackInitial.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint-disable max-len */ - -import type { ApiOldLangPack } from '../api/types'; - -export const fallbackLangPackInitial = { - WrongNumber: 'Wrong number?', - SentAppCode: 'We\'ve sent the code to the **Telegram** app on your other device.', - 'Login.JustSentSms': 'We have sent you a code via SMS. Please enter it above.', - 'Login.Header.Password': 'Enter Password', - 'Login.EnterPasswordDescription': 'You have Two-Step Verification enabled, so your account is protected with an additional password.', - StartText: 'Please confirm your country code and enter your phone number.', - 'Login.PhonePlaceholder': 'Your phone number', - 'Login.Next': 'Next', - 'Login.QR.Login': 'Log in by QR Code', - 'Login.QR.Title': 'Log in to Telegram by QR Code', - 'Login.QR.Help1': 'Open Telegram on your phone', - 'Login.QR.Help2': 'Go to **Settings** > **Devices** > **Link Desktop Device**', - 'Login.QR2.Help2': 'Go to **Settings** → **Devices** → **Link Desktop Device**', - 'Login.QR.Help3': 'Point your phone at this screen to confirm login', - 'Login.QR.Cancel': 'Log in by phone Number', - YourName: 'Your Name', - 'Login.Register.Desc': 'Enter your name and add a profile picture.', - 'Login.Register.FirstName.Placeholder': 'First Name', - 'Login.Register.LastName.Placeholder': 'Last Name', - 'Login.SelectCountry.Title': 'Country', - lng_country_none: 'Country not found', - PleaseEnterPassword: 'Enter your new password', - PHONE_NUMBER_INVALID: 'Invalid phone number', - PHONE_CODE_INVALID: 'Invalid code', - PASSWORD_HASH_INVALID: 'Incorrect password', - PHONE_PASSWORD_FLOOD: 'Limit exceeded. Please try again later.', - PHONE_NUMBER_BANNED: 'This phone number is banned.', -} as ApiOldLangPack; diff --git a/src/util/localization/index.ts b/src/util/localization/index.ts index 4525790f4..f4243a997 100644 --- a/src/util/localization/index.ts +++ b/src/util/localization/index.ts @@ -320,10 +320,17 @@ function createTranslationFn(): LangFn { } return processTranslation(key, variables as Record, options); }); + fn.withRegular = (({ key, variables, options }: RegularLangFnParameters) => { + return processTranslation(key, variables, options); + }); + fn.withAdvanced = (({ key, variables, options }: AdvancedLangFnParameters) => { + return processTranslationAdvanced(key, variables, options); + }); fn.region = (code: string) => formatters?.region.of(code); fn.conjunction = (list: string[]) => formatters?.conjunction.format(list) || list.join(', '); fn.disjunction = (list: string[]) => formatters?.disjunction.format(list) || list.join(', '); fn.number = (value: number) => formatters?.number.format(value) || String(value); + fn.languageInfo = language!; return fn; } diff --git a/src/util/localization/types.ts b/src/util/localization/types.ts index 89fee27f4..0a1dd412b 100644 --- a/src/util/localization/types.ts +++ b/src/util/localization/types.ts @@ -1,6 +1,7 @@ import type { TeactNode } from '../../lib/teact/teact'; import type { + ApiLanguage, LangPackStringValue, LangPackStringValueDeleted, LangPackStringValuePlural, @@ -143,6 +144,8 @@ export type LangFn = { ): TeactNode; with: (params: LangFnParameters) => TeactNode; + withRegular: (params: RegularLangFnParameters) => string; + withAdvanced: (params: AdvancedLangFnParameters) => TeactNode; region: (code: string) => string | undefined; conjunction: (list: string[]) => string; disjunction: (list: string[]) => string; @@ -150,6 +153,7 @@ export type LangFn = { isRtl?: boolean; code: string; pluralCode: string; + languageInfo: ApiLanguage; }; type ListFormat = Pick; diff --git a/src/util/oldLangProvider.ts b/src/util/oldLangProvider.ts index 01b498161..1b63e1c98 100644 --- a/src/util/oldLangProvider.ts +++ b/src/util/oldLangProvider.ts @@ -4,12 +4,11 @@ import type { ApiOldLangPack, ApiOldLangString } from '../api/types'; import type { LangCode, TimeFormat } from '../types'; import { - DEFAULT_LANG_CODE, LANG_CACHE_NAME, LANG_PACKS, OLD_DEFAULT_LANG_PACK, + DEFAULT_LANG_CODE, LANG_CACHE_NAME, LANG_PACKS, } from '../config'; import { callApi } from '../api/gramjs'; import * as cacheApi from './cacheApi'; import { createCallbackManager } from './callbacks'; -import { fallbackLangPackInitial } from './fallbackLangPackInitial'; import { loadAndChangeLanguage } from './localization'; import { formatInteger } from './textFormat'; @@ -129,7 +128,7 @@ function createLangFn() { void importFallbackLangPack(); } - const langString = langPack?.[key] || fallbackLangPack?.[key] || fallbackLangPackInitial[key]; + const langString = langPack?.[key] || fallbackLangPack?.[key]; if (!langString) { return key; } @@ -151,23 +150,6 @@ export function getTranslationFn(): LangFn { return translationFn; } -export async function getTranslationForLangString(langCode: string, key: string) { - let translateString: ApiOldLangString | undefined; - const cachedValue = await cacheApi.fetch( - LANG_CACHE_NAME, - `${OLD_DEFAULT_LANG_PACK}_${langCode}_${key}`, - cacheApi.Type.Json, - ); - - if (cachedValue) { - translateString = cachedValue.value; - } else { - translateString = await fetchRemoteString(OLD_DEFAULT_LANG_PACK, langCode, key); - } - - return processTranslation(translateString, key); -} - /** * @deprecated Migrate to `changeLanguage` in `util/localization.ts` instead */ @@ -246,28 +228,6 @@ async function fetchRemote(langCode: string): Promise { - const remote = await callApi('oldFetchLangStrings', { - langPack: remoteLangPack, - langCode, - keys: [key], - }); - - if (remote?.length) { - const wrappedString = JSON.stringify({ - value: remote[0], - }); - - await cacheApi.save(LANG_CACHE_NAME, `${remoteLangPack}_${langCode}_${key}`, wrappedString); - - return remote[0]; - } - - return undefined; -} - function getPluralOption(amount: number) { const langCode = currentLangCode || DEFAULT_LANG_CODE; const optionIndex = PLURAL_RULES[langCode as keyof typeof PLURAL_RULES]