Localization: Migrate more components (#5484)

This commit is contained in:
zubiden 2025-01-21 18:21:57 +01:00 committed by Alexander Zinchuk
parent cfad59e2f0
commit 8bac803dea
33 changed files with 445 additions and 354 deletions

View File

@ -5,14 +5,16 @@ import readFallbackStrings from '../src/util/data/readFallbackStrings';
import { pick } from '../src/util/iteratees'; import { pick } from '../src/util/iteratees';
const HEADER = `/* eslint-disable */ 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() { async function main() {
const data = await readFallbackStrings(true); const data = await readFallbackStrings(true);
const selectedKeys = pick(data.langPack.strings, initialKeys); const selectedKeys = pick(data.langPack.strings, initialKeys);
const json = JSON.stringify(selectedKeys, undefined, 2); const json = JSON.stringify(selectedKeys, undefined, 2);
const text = `${HEADER}\nexport default ${json} as Record<string, string>;\n`; const text = `${HEADER}\nexport default ${json} as Record<LangKey, LangPackStringValue>;\n`;
writeFileSync('./src/assets/localization/initialStrings.ts', text, 'utf8'); writeFileSync('./src/assets/localization/initialStrings.ts', text, 'utf8');
} }

View File

@ -1,5 +1,7 @@
import { FloodWaitError, RPCError } from '../../../lib/gramjs/errors'; import { FloodWaitError, RPCError } from '../../../lib/gramjs/errors';
import type { RegularLangKey } from '../../../types/language';
import type { RegularLangFnParameters } from '../../../util/localization';
import type { import type {
ApiUpdateAuthorizationState, ApiUpdateAuthorizationState,
ApiUpdateAuthorizationStateType, ApiUpdateAuthorizationStateType,
@ -10,12 +12,12 @@ import type {
import { DEBUG } from '../../../config'; import { DEBUG } from '../../../config';
import { sendApiUpdate } from '../updates/apiUpdateEmitter'; import { sendApiUpdate } from '../updates/apiUpdateEmitter';
const ApiErrors: { [k: string]: string } = { const ApiErrors: Record<string, RegularLangKey> = {
PHONE_NUMBER_INVALID: 'Invalid phone number.', PHONE_NUMBER_INVALID: 'ErrorPhoneNumberInvalid',
PHONE_CODE_INVALID: 'Invalid code.', PHONE_CODE_INVALID: 'ErrorCodeInvalid',
PASSWORD_HASH_INVALID: 'Incorrect password.', PASSWORD_HASH_INVALID: 'ErrorIncorrectPassword',
PHONE_PASSWORD_FLOOD: 'Limit exceeded. Please try again later.', PHONE_PASSWORD_FLOOD: 'ErrorPasswordFlood',
PHONE_NUMBER_BANNED: 'This phone number is banned.', PHONE_NUMBER_BANNED: 'ErrorPhoneBanned',
}; };
const authController: { const authController: {
@ -85,19 +87,30 @@ export function onRequestQrCode(qrCode: { token: Buffer; expires: number }) {
} }
export function onAuthError(err: Error) { export function onAuthError(err: Error) {
let message: string; let messageKey: RegularLangFnParameters | undefined;
if (err instanceof FloodWaitError) { if (err instanceof FloodWaitError) {
const hours = Math.ceil(Number(err.seconds) / 60 / 60); 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) { } else if (err instanceof RPCError) {
message = ApiErrors[err.errorMessage]; messageKey = {
} else { key: ApiErrors[err.errorMessage],
message = err.message; };
} else if (err.message) {
messageKey = {
key: 'ErrorUnexpectedMessage',
variables: { error: err.message },
};
} }
if (!message) { if (!messageKey) {
message = 'Unexpected Error'; messageKey = {
key: 'ErrorUnexpected',
};
if (DEBUG) { if (DEBUG) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -107,7 +120,7 @@ export function onAuthError(err: Error) {
sendApiUpdate({ sendApiUpdate({
'@type': 'updateAuthorizationError', '@type': 'updateAuthorizationError',
message, errorKey: messageKey,
}); });
} }

View File

@ -17,8 +17,8 @@ import type {
import { import {
ACCEPTABLE_USERNAME_ERRORS, ACCEPTABLE_USERNAME_ERRORS,
BLOCKED_LIST_LIMIT, BLOCKED_LIST_LIMIT,
LANG_PACK,
MAX_INT_32, MAX_INT_32,
OLD_DEFAULT_LANG_PACK,
} from '../../../config'; } from '../../../config';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';
@ -36,7 +36,6 @@ import {
buildApiWebSession, buildApiWebSession,
buildLangStrings, buildLangStrings,
oldBuildLangPack, oldBuildLangPack,
oldBuildLangPackString,
} from '../apiBuilders/misc'; } from '../apiBuilders/misc';
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import { import {
@ -468,7 +467,7 @@ export async function fetchLangDifference({
export async function fetchLanguages(): Promise<ApiLanguage[] | undefined> { export async function fetchLanguages(): Promise<ApiLanguage[] | undefined> {
const result = await invokeRequest(new GramJs.langpack.GetLanguages({ const result = await invokeRequest(new GramJs.langpack.GetLanguages({
langPack: OLD_DEFAULT_LANG_PACK, langPack: LANG_PACK,
})); }));
if (!result) { if (!result) {
return undefined; return undefined;
@ -495,6 +494,27 @@ export async function fetchLanguage({
return buildApiLanguage(result); 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 }: { export async function oldFetchLangPack({ sourceLangPacks, langCode }: {
sourceLangPacks: typeof LANG_PACKS; sourceLangPacks: typeof LANG_PACKS;
langCode: string; langCode: string;
@ -514,22 +534,6 @@ export async function oldFetchLangPack({ sourceLangPacks, langCode }: {
return { langPack: Object.assign({}, ...collections.reverse()) as typeof collections[0] }; 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) { export async function fetchPrivacySettings(privacyKey: ApiPrivacyKey) {
const key = buildInputPrivacyKey(privacyKey); const key = buildInputPrivacyKey(privacyKey);
const result = await invokeRequest(new GramJs.account.GetPrivacy({ key })); const result = await invokeRequest(new GramJs.account.GetPrivacy({ key }));

View File

@ -7,6 +7,7 @@ import type {
VideoState, VideoState,
} from '../../lib/secret-sauce'; } from '../../lib/secret-sauce';
import type { ThreadId } from '../../types'; import type { ThreadId } from '../../types';
import type { RegularLangFnParameters } from '../../util/localization';
import type { ApiBotMenuButton } from './bots'; import type { ApiBotMenuButton } from './bots';
import type { import type {
ApiGroupCall, ApiPhoneCall, ApiGroupCall, ApiPhoneCall,
@ -84,7 +85,7 @@ export type ApiUpdateSession = {
export type ApiUpdateAuthorizationError = { export type ApiUpdateAuthorizationError = {
'@type': 'updateAuthorizationError'; '@type': 'updateAuthorizationError';
message: string; errorKey: RegularLangFnParameters;
}; };
export type ApiUpdateConnectionState = { export type ApiUpdateConnectionState = {

View File

@ -162,7 +162,7 @@
"LoginQRLogin" = "Log in by QR Code"; "LoginQRLogin" = "Log in by QR Code";
"LoginQRTitle" = "Log in to Telegram by QR Code"; "LoginQRTitle" = "Log in to Telegram by QR Code";
"LoginQRHelp1" = "Open Telegram on your phone"; "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"; "LoginQRHelp3" = "Point your phone at this screen to confirm login";
"LoginQRCancel" = "Log in by phone Number"; "LoginQRCancel" = "Log in by phone Number";
"YourName" = "Your Name"; "YourName" = "Your Name";
@ -394,6 +394,8 @@
"AuthSessionsViewBrowser" = "Browser"; "AuthSessionsViewBrowser" = "Browser";
"AuthSessionsViewLocationInfo" = "This location estimate is based on the IP address and may not always be accurate."; "AuthSessionsViewLocationInfo" = "This location estimate is based on the IP address and may not always be accurate.";
"AuthSessionsLogOutApplications" = "Disconnect All Websites"; "AuthSessionsLogOutApplications" = "Disconnect All Websites";
"AuthKeepSignedIn" = "Keep me signed in";
"AuthTitle" = "Telegram";
"ClearOtherWebSessionsHelp" = "You can log in on websites that support signing in with 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?"; "AreYouSureWebSessions" = "Are you sure you want to disconnect all websites where you logged in with Telegram?";
"AutodownloadSizeLimitUpTo" = "up to {limit}"; "AutodownloadSizeLimitUpTo" = "up to {limit}";
@ -421,10 +423,17 @@
"SettingsSendEnter" = "Send with Enter"; "SettingsSendEnter" = "Send with Enter";
"SettingsSendCmdenter" = "Send with Cmd+Enter"; "SettingsSendCmdenter" = "Send with Cmd+Enter";
"SettingsSendCtrlenter" = "Send with Ctrl+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"; "TextSize" = "Message Font Size";
"ChatBackground" = "Chat Wallpaper"; "ChatBackground" = "Chat Wallpaper";
"Theme" = "Theme"; "Theme" = "Theme";
"VoiceOverKeyboard" = "Keyboard";
"AccDescrStickers" = "Stickers"; "AccDescrStickers" = "Stickers";
"DoubleTapSetting" = "Quick Reaction"; "DoubleTapSetting" = "Quick Reaction";
"SuggestStickers" = "Sticker suggestions by emoji"; "SuggestStickers" = "Sticker suggestions by emoji";
@ -625,10 +634,49 @@
"PollsSolutionTitle" = "Explanation"; "PollsSolutionTitle" = "Explanation";
"CreatePollExplanationInfo" = "Users will see this comment after choosing a wrong answer, good for educational purposes."; "CreatePollExplanationInfo" = "Users will see this comment after choosing a wrong answer, good for educational purposes.";
"VoipGroupPersonalAccount" = "personal account"; "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"; "FavoriteStickers" = "Favorites";
"PremiumStickers" = "Premium Stickers"; "PremiumStickers" = "Premium Stickers";
"GroupStickers" = "Group Stickers"; "GroupStickers" = "Group Stickers";
"ErrorSendRestrictedStickersAll" = "Sorry, sending stickers is not allowed in this group."; "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"; "NoStickers" = "No stickers yet";
"ClearRecentEmoji" = "Clear recent emoji?"; "ClearRecentEmoji" = "Clear recent emoji?";
"TextFormatAddLinkTitle" = "Add Link"; "TextFormatAddLinkTitle" = "Add Link";
@ -1343,8 +1391,8 @@
"GiftInfoConvertTitle" = "Convert Gift to Stars"; "GiftInfoConvertTitle" = "Convert Gift to Stars";
"GiftInfoConvertDescription1" = "Do you want to convert this gift from **{user}** to **{amount}**?"; "GiftInfoConvertDescription1" = "Do you want to convert this gift from **{user}** to **{amount}**?";
"GiftInfoConvertDescription2" = "This action cannot be undone. This will permanently destroy the gift."; "GiftInfoConvertDescription2" = "This action cannot be undone. This will permanently destroy the gift.";
"GiftInfoConvertDescriptionPeriod_one" = "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**." "GiftInfoConvertDescriptionPeriod_other" = "Conversion is available for the next **{count} days**.";
"GiftInfoSaved" = "This gift is visible on your profile. {link}"; "GiftInfoSaved" = "This gift is visible on your profile. {link}";
"GiftInfoSavedView" = "View >"; "GiftInfoSavedView" = "View >";
"GiftInfoHidden" = "This gift is hidden. Only you can see it."; "GiftInfoHidden" = "This gift is hidden. Only you can see it.";
@ -1363,10 +1411,10 @@
"GiftAttributeModel" = "Model"; "GiftAttributeModel" = "Model";
"GiftAttributeBackdrop" = "Backdrop"; "GiftAttributeBackdrop" = "Backdrop";
"GiftAttributeSymbol" = "Symbol"; "GiftAttributeSymbol" = "Symbol";
"GiftInfoOriginalInfo" = "Gifted to {user} on {date}." "GiftInfoOriginalInfo" = "Gifted to {user} on {date}.";
"GiftInfoOriginalInfoSender" = "Gifted by {sender} to {user} on {date}." "GiftInfoOriginalInfoSender" = "Gifted by {sender} to {user} on {date}.";
"GiftInfoOriginalInfoText" = "Gifted to {user} on {date} with comment \"{text}\"." "GiftInfoOriginalInfoText" = "Gifted to {user} on {date} with comment \"{text}\".";
"GiftInfoOriginalInfoTextSender" = "Gifted by {sender} to {user} on {date} with comment \"{text}\"." "GiftInfoOriginalInfoTextSender" = "Gifted by {sender} to {user} on {date} with comment \"{text}\".";
"GiftInfoStatus" = "Status"; "GiftInfoStatus" = "Status";
"GiftInfoStatusNonUnique" = "Non-Unique"; "GiftInfoStatusNonUnique" = "Non-Unique";
"GiftInfoViewUpgraded" = "View Upgraded Gift"; "GiftInfoViewUpgraded" = "View Upgraded Gift";
@ -1399,7 +1447,7 @@
"ReceivedGift" = "Received Gift"; "ReceivedGift" = "Received Gift";
"SentGift" = "Sent Gift"; "SentGift" = "Sent Gift";
"StarGiftInfoDescriptionInbound" = "You can keep this gift in your Profile or convert it to {count} Stars. {link}"; "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 >"; "StarGiftInfoLinkCaption" = "More About Stars >";
"StarGiftDisplayOnMyPage" = "Display on on my page"; "StarGiftDisplayOnMyPage" = "Display on on my page";
"StarGiftConvertTo" = "Convert to"; "StarGiftConvertTo" = "Convert to";
@ -1435,12 +1483,12 @@
"BotDownloadFileDescription" = "**{bot}** suggests you to download **{filename}**"; "BotDownloadFileDescription" = "**{bot}** suggests you to download **{filename}**";
"BotDownloadFileButton" = "Download"; "BotDownloadFileButton" = "Download";
"PrivacyGifts" = "Gifts"; "PrivacyGifts" = "Gifts";
"PrivacyGiftsTitle" = "Who can display gifts on my profile?" "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." "PrivacyGiftsInfo" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile.";
"PrivacyValueBots" = "Mini Apps" "PrivacyValueBots" = "Mini Apps";
"CustomShareGiftsInfo" = "You can add users or entire groups as exceptions that will override the settings above." "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_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?" "StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?";
"StarsSubscribeBotButtonMonth" = "Subscribe for {amount} / month"; "StarsSubscribeBotButtonMonth" = "Subscribe for {amount} / month";
"AllChatsSearchContext" = "All Chats"; "AllChatsSearchContext" = "All Chats";
"PrivateChatsSearchContext" = "Private Chats"; "PrivateChatsSearchContext" = "Private Chats";
@ -1450,9 +1498,9 @@
"FolderLinkTitleDescription" = "Anyone with this link can add {folder} folder and {chats} selected below."; "FolderLinkTitleDescription" = "Anyone with this link can add {folder} folder and {chats} selected below.";
"FolderLinkTitleDescriptionChats_one" = "the chat"; "FolderLinkTitleDescriptionChats_one" = "the chat";
"FolderLinkTitleDescriptionChats_other" = "the {count} chats"; "FolderLinkTitleDescriptionChats_other" = "the {count} chats";
"FolderLinkSubtitleNew" = "Do you want to add a new chat folder and join its groups and channels?" "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." "FolderLinkSubtitleAlready" = "You have already added this folder and its chats.";
"FolderLinkSubtitleAdd" = "Do you want to add {chats} to the folder **{title}**?" "FolderLinkSubtitleAdd" = "Do you want to add {chats} to the folder **{title}**?";
"FolderLinkSubtitleAddCount_one" = "1 chat"; "FolderLinkSubtitleAddCount_one" = "1 chat";
"FolderLinkSubtitleAddCount_other" = "{count} chats"; "FolderLinkSubtitleAddCount_other" = "{count} chats";
"FolderLinkAddFolder" = "Add Folder"; "FolderLinkAddFolder" = "Add Folder";
@ -1496,7 +1544,7 @@
"ActionUnsupportedDescription" = "Please, use one of our apps to complete this action."; "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}**."; "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"; "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"; "GiftWasNotFound" = "Gift was not found";
"ViewButtonRequestJoin" = "REQUEST TO JOIN"; "ViewButtonRequestJoin" = "REQUEST TO JOIN";
"ViewButtonMessage" = "VIEW MESSAGE"; "ViewButtonMessage" = "VIEW MESSAGE";
@ -1511,4 +1559,5 @@
"ViewButtonStory" = "VIEW STORY"; "ViewButtonStory" = "VIEW STORY";
"ViewButtonBoost" = "BOOST"; "ViewButtonBoost" = "BOOST";
"ViewButtonStickerset" = "VIEW STICKERS"; "ViewButtonStickerset" = "VIEW STICKERS";
"ViewButtonGiftUnique" = "VIEW COLLECTIBLE"; "ViewButtonGiftUnique" = "VIEW COLLECTIBLE";
"AuthContinueOnThisLanguage" = "Continue in English";

View File

@ -1,4 +1,6 @@
const INITIAL_KEYS = [ import type { LangKey } from '../../types/language';
const INITIAL_KEYS: LangKey[] = [
'WrongNumber', 'WrongNumber',
'SentAppCode', 'SentAppCode',
'LoginJustSentSms', 'LoginJustSentSms',
@ -11,7 +13,7 @@ const INITIAL_KEYS = [
'LoginQRTitle', 'LoginQRTitle',
'LoginQRHelp1', 'LoginQRHelp1',
'LoginQRHelp2', 'LoginQRHelp2',
'LoginQR2Help2', 'LoginQRHelp2',
'LoginQRHelp3', 'LoginQRHelp3',
'LoginQRCancel', 'LoginQRCancel',
'YourName', 'YourName',
@ -21,6 +23,14 @@ const INITIAL_KEYS = [
'LoginSelectCountryTitle', 'LoginSelectCountryTitle',
'CountryNone', 'CountryNone',
'PleaseEnterPassword', 'PleaseEnterPassword',
'ErrorPhoneNumberInvalid',
'ErrorCodeInvalid',
'ErrorIncorrectPassword',
'ErrorPasswordFlood',
'ErrorPhoneBanned',
'ErrorFlood',
'ErrorUnexpected',
'ErrorUnexpectedMessage',
]; ];
export default INITIAL_KEYS; export default INITIAL_KEYS;

View File

@ -1,19 +1,22 @@
/* eslint-disable */ /* eslint-disable */
// This file is generated by dev/generateInitialLangFallback.ts. Do not edit it manually. // 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 { export default {
"WrongNumber": "Wrong number?", "WrongNumber": "Wrong number?",
"SentAppCode": "We've sent the code to the **Telegram** app on your other device.", "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.", "LoginJustSentSms": "We've sent you a code via SMS. Please enter it above.",
"LoginHeaderPassword": "Enter Password", "LoginHeaderPassword": "Enter Password",
"LoginEnterPasswordDescription": "You have Two-Step Verification enabled, so your account is protected with an additional 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", "LoginPhonePlaceholder": "Your phone number",
"LoginNext": "Next", "LoginNext": "Next",
"LoginQRLogin": "Log in by QR Code", "LoginQRLogin": "Log in by QR Code",
"LoginQRTitle": "Log in to Telegram by QR Code", "LoginQRTitle": "Log in to Telegram by QR Code",
"LoginQRHelp1": "Open Telegram on your phone", "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", "LoginQRHelp3": "Point your phone at this screen to confirm login",
"LoginQRCancel": "Log in by phone Number", "LoginQRCancel": "Log in by phone Number",
"YourName": "Your Name", "YourName": "Your Name",
@ -22,5 +25,16 @@ export default {
"LoginRegisterLastNamePlaceholder": "Last Name", "LoginRegisterLastNamePlaceholder": "Last Name",
"LoginSelectCountryTitle": "Country", "LoginSelectCountryTitle": "Country",
"CountryNone": "Country not found", "CountryNone": "Country not found",
"PleaseEnterPassword": "Enter your new password" "PleaseEnterPassword": "Enter your new password",
} as Record<string, string>; "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<LangKey, LangPackStringValue>;

View File

@ -9,16 +9,16 @@ import type { GlobalState } from '../../global/types';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import useHistoryBack from '../../hooks/useHistoryBack'; 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 TrackingMonkey from '../common/TrackingMonkey';
import InputText from '../ui/InputText'; import InputText from '../ui/InputText';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
type StateProps = Pick<GlobalState, 'authPhoneNumber' | 'authIsCodeViaApp' | 'authIsLoading' | 'authError'>; type StateProps = Pick<GlobalState, 'authPhoneNumber' | 'authIsCodeViaApp' | 'authIsLoading' | 'authErrorKey'>;
const CODE_LENGTH = 5; const CODE_LENGTH = 5;
@ -26,15 +26,15 @@ const AuthCode: FC<StateProps> = ({
authPhoneNumber, authPhoneNumber,
authIsCodeViaApp, authIsCodeViaApp,
authIsLoading, authIsLoading,
authError, authErrorKey,
}) => { }) => {
const { const {
setAuthCode, setAuthCode,
returnToAuthPhoneNumber, returnToAuthPhoneNumber,
clearAuthError, clearAuthErrorKey,
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@ -54,8 +54,8 @@ const AuthCode: FC<StateProps> = ({
}); });
const onCodeChange = useCallback((e: FormEvent<HTMLInputElement>) => { const onCodeChange = useCallback((e: FormEvent<HTMLInputElement>) => {
if (authError) { if (authErrorKey) {
clearAuthError(); clearAuthErrorKey();
} }
const { currentTarget: target } = e; const { currentTarget: target } = e;
@ -82,7 +82,7 @@ const AuthCode: FC<StateProps> = ({
if (target.value.length === CODE_LENGTH) { if (target.value.length === CODE_LENGTH) {
setAuthCode({ code: target.value }); setAuthCode({ code: target.value });
} }
}, [authError, clearAuthError, code, isTracking, setAuthCode]); }, [authErrorKey, code, isTracking, setAuthCode]);
function handleReturnToAuthPhoneNumber() { function handleReturnToAuthPhoneNumber() {
returnToAuthPhoneNumber(); returnToAuthPhoneNumber();
@ -107,11 +107,14 @@ const AuthCode: FC<StateProps> = ({
title={lang('WrongNumber')} title={lang('WrongNumber')}
aria-label={lang('WrongNumber')} aria-label={lang('WrongNumber')}
> >
<i className="icon icon-edit" /> <Icon name="edit" />
</div> </div>
</h1> </h1>
<p className="note"> <p className="note">
{renderText(lang(authIsCodeViaApp ? 'SentAppCode' : 'Login.JustSentSms'), ['simple_markdown'])} {lang(authIsCodeViaApp ? 'SentAppCode' : 'LoginJustSentSms', undefined, {
withNodes: true,
withMarkdown: true,
})}
</p> </p>
<InputText <InputText
ref={inputRef} ref={inputRef}
@ -119,7 +122,7 @@ const AuthCode: FC<StateProps> = ({
label={lang('Code')} label={lang('Code')}
onInput={onCodeChange} onInput={onCodeChange}
value={code} value={code}
error={authError && lang(authError)} error={authErrorKey && lang.withRegular(authErrorKey)}
autoComplete="off" autoComplete="off"
inputMode="numeric" inputMode="numeric"
/> />
@ -130,5 +133,5 @@ const AuthCode: FC<StateProps> = ({
}; };
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => pick(global, ['authPhoneNumber', 'authIsCodeViaApp', 'authIsLoading', 'authError']), (global): StateProps => pick(global, ['authPhoneNumber', 'authIsCodeViaApp', 'authIsLoading', 'authErrorKey']),
)(AuthCode)); )(AuthCode));

View File

@ -6,19 +6,19 @@ import type { GlobalState } from '../../global/types';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import useOldLang from '../../hooks/useOldLang'; import useLang from '../../hooks/useLang';
import PasswordForm from '../common/PasswordForm'; import PasswordForm from '../common/PasswordForm';
import MonkeyPassword from '../common/PasswordMonkey'; import MonkeyPassword from '../common/PasswordMonkey';
type StateProps = Pick<GlobalState, 'authIsLoading' | 'authError' | 'authHint'>; type StateProps = Pick<GlobalState, 'authIsLoading' | 'authErrorKey' | 'authHint'>;
const AuthPassword: FC<StateProps> = ({ const AuthPassword: FC<StateProps> = ({
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 [showPassword, setShowPassword] = useState(false);
const handleChangePasswordVisibility = useCallback((isVisible) => { const handleChangePasswordVisibility = useCallback((isVisible) => {
@ -33,11 +33,11 @@ const AuthPassword: FC<StateProps> = ({
<div id="auth-password-form" className="custom-scroll"> <div id="auth-password-form" className="custom-scroll">
<div className="auth-form"> <div className="auth-form">
<MonkeyPassword isPasswordVisible={showPassword} /> <MonkeyPassword isPasswordVisible={showPassword} />
<h1>{lang('Login.Header.Password')}</h1> <h1>{lang('LoginHeaderPassword')}</h1>
<p className="note">{lang('Login.EnterPasswordDescription')}</p> <p className="note">{lang('LoginEnterPasswordDescription')}</p>
<PasswordForm <PasswordForm
clearError={clearAuthError} clearError={clearAuthErrorKey}
error={authError && lang(authError)} error={authErrorKey && lang.withRegular(authErrorKey)}
hint={authHint} hint={authHint}
isLoading={authIsLoading} isLoading={authIsLoading}
isPasswordVisible={showPassword} isPasswordVisible={showPassword}
@ -50,5 +50,5 @@ const AuthPassword: FC<StateProps> = ({
}; };
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => pick(global, ['authIsLoading', 'authError', 'authHint']), (global): StateProps => pick(global, ['authIsLoading', 'authErrorKey', 'authHint']),
)(AuthPassword)); )(AuthPassword));

View File

@ -18,8 +18,8 @@ import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useOldLang from '../../hooks/useOldLang'; import useLang from '../../hooks/useLang';
import useOldLangString from '../../hooks/useOldLangString'; import useLangString from '../../hooks/useLangString';
import Button from '../ui/Button'; import Button from '../ui/Button';
import Checkbox from '../ui/Checkbox'; import Checkbox from '../ui/Checkbox';
@ -32,7 +32,7 @@ import monkeyPath from '../../assets/monkey.svg';
type StateProps = Pick<GlobalState, ( type StateProps = Pick<GlobalState, (
'connectionState' | 'authState' | 'connectionState' | 'authState' |
'authPhoneNumber' | 'authIsLoading' | 'authPhoneNumber' | 'authIsLoading' |
'authIsLoadingQrCode' | 'authError' | 'authIsLoadingQrCode' | 'authErrorKey' |
'authRememberMe' | 'authNearestCountry' 'authRememberMe' | 'authNearestCountry'
)> & { )> & {
language?: string; language?: string;
@ -49,7 +49,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
authPhoneNumber, authPhoneNumber,
authIsLoading, authIsLoading,
authIsLoadingQrCode, authIsLoadingQrCode,
authError, authErrorKey,
authRememberMe, authRememberMe,
authNearestCountry, authNearestCountry,
phoneCodeList, phoneCodeList,
@ -60,18 +60,18 @@ const AuthPhoneNumber: FC<StateProps> = ({
setAuthRememberMe, setAuthRememberMe,
loadNearestCountry, loadNearestCountry,
loadCountryList, loadCountryList,
clearAuthError, clearAuthErrorKey,
goToAuthQrCode, goToAuthQrCode,
setSettingOption, setSettingOption,
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const suggestedLanguage = getSuggestedLanguage(); const suggestedLanguage = getSuggestedLanguage();
const isConnected = connectionState === 'connectionStateReady'; const isConnected = connectionState === 'connectionStateReady';
const continueText = useOldLangString(isConnected ? suggestedLanguage : undefined, 'ContinueOnThisLanguage', true); const continueText = useLangString('AuthContinueOnThisLanguage', suggestedLanguage);
const [country, setCountry] = useState<ApiCountryCode | undefined>(); const [country, setCountry] = useState<ApiCountryCode | undefined>();
const [phoneNumber, setPhoneNumber] = useState<string | undefined>(); const [phoneNumber, setPhoneNumber] = useState<string | undefined>();
const [isTouched, setIsTouched] = useState(false); const [isTouched, setIsTouched] = useState(false);
@ -161,8 +161,8 @@ const AuthPhoneNumber: FC<StateProps> = ({
}, []); }, []);
const handlePhoneNumberChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handlePhoneNumberChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
if (authError) { if (authErrorKey) {
clearAuthError(); clearAuthErrorKey();
} }
// This is for further screens. We delay it until user input to speed up the initial loading. // This is for further screens. We delay it until user input to speed up the initial loading.
@ -186,7 +186,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
&& value.length - fullNumber.length > 1 && !isJustPastedRef.current && value.length - fullNumber.length > 1 && !isJustPastedRef.current
); );
parseFullNumber(shouldFixSafariAutoComplete ? `${country!.countryCode} ${value}` : value); parseFullNumber(shouldFixSafariAutoComplete ? `${country!.countryCode} ${value}` : value);
}, [authError, clearAuthError, country, fullNumber, parseFullNumber]); }, [authErrorKey, country, fullNumber, parseFullNumber]);
const handleKeepSessionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleKeepSessionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setAuthRememberMe(e.target.checked); setAuthRememberMe(e.target.checked);
@ -214,7 +214,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
<div id="auth-phone-number-form" className="custom-scroll"> <div id="auth-phone-number-form" className="custom-scroll">
<div className="auth-form"> <div className="auth-form">
<div id="logo" /> <div id="logo" />
<h1>Telegram</h1> <h1>{lang('AuthTitle')}</h1>
<p className="note">{lang('StartText')}</p> <p className="note">{lang('StartText')}</p>
<form className="form" action="" onSubmit={handleSubmit}> <form className="form" action="" onSubmit={handleSubmit}>
<CountryCodeInput <CountryCodeInput
@ -226,29 +226,29 @@ const AuthPhoneNumber: FC<StateProps> = ({
<InputText <InputText
ref={inputRef} ref={inputRef}
id="sign-in-phone-number" id="sign-in-phone-number"
label={lang('Login.PhonePlaceholder')} label={lang('LoginPhonePlaceholder')}
value={fullNumber} value={fullNumber}
error={authError && lang(authError)} error={authErrorKey && lang.withRegular(authErrorKey)}
inputMode="tel" inputMode="tel"
onChange={handlePhoneNumberChange} onChange={handlePhoneNumberChange}
onPaste={IS_SAFARI ? handlePaste : undefined} onPaste={IS_SAFARI ? handlePaste : undefined}
/> />
<Checkbox <Checkbox
id="sign-in-keep-session" id="sign-in-keep-session"
label="Keep me signed in" label={lang('AuthKeepSignedIn')}
checked={Boolean(authRememberMe)} checked={Boolean(authRememberMe)}
onChange={handleKeepSessionChange} onChange={handleKeepSessionChange}
/> />
{canSubmit && ( {canSubmit && (
isAuthReady ? ( isAuthReady ? (
<Button type="submit" ripple isLoading={authIsLoading}>{lang('Login.Next')}</Button> <Button type="submit" ripple isLoading={authIsLoading}>{lang('LoginNext')}</Button>
) : ( ) : (
<Loading /> <Loading />
) )
)} )}
{isAuthReady && ( {isAuthReady && (
<Button isText ripple isLoading={authIsLoadingQrCode} onClick={handleGoToAuthQrCode}> <Button isText ripple isLoading={authIsLoadingQrCode} onClick={handleGoToAuthQrCode}>
{lang('Login.QR.Login')} {lang('LoginQRLogin')}
</Button> </Button>
)} )}
{suggestedLanguage && suggestedLanguage !== language && continueText && ( {suggestedLanguage && suggestedLanguage !== language && continueText && (
@ -274,7 +274,7 @@ export default memo(withGlobal(
'authPhoneNumber', 'authPhoneNumber',
'authIsLoading', 'authIsLoading',
'authIsLoadingQrCode', 'authIsLoadingQrCode',
'authError', 'authErrorKey',
'authRememberMe', 'authRememberMe',
'authNearestCountry', 'authNearestCountry',
]), ]),

View File

@ -1,4 +1,3 @@
import type { FC } from '../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, memo, useCallback, useEffect, useLayoutEffect, useRef,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
@ -11,14 +10,13 @@ import { disableStrict, enableStrict } from '../../lib/fasterdom/stricterdom';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { oldSetLanguage } from '../../util/oldLangProvider'; import { oldSetLanguage } from '../../util/oldLangProvider';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import renderText from '../common/helpers/renderText';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import useAsync from '../../hooks/useAsync'; import useAsync from '../../hooks/useAsync';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import useLangString from '../../hooks/useLangString';
import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated'; import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated';
import useOldLang from '../../hooks/useOldLang';
import useOldLangString from '../../hooks/useOldLangString';
import AnimatedIcon from '../common/AnimatedIcon'; import AnimatedIcon from '../common/AnimatedIcon';
import Button from '../ui/Button'; import Button from '../ui/Button';
@ -44,24 +42,24 @@ function ensureQrCodeStyling() {
return qrCodeStylingPromise; return qrCodeStylingPromise;
} }
const AuthCode: FC<StateProps> = ({ const AuthCode = ({
connectionState, connectionState,
authState, authState,
authQrCode, authQrCode,
language, language,
}) => { }: StateProps) => {
const { const {
returnToAuthPhoneNumber, returnToAuthPhoneNumber,
setSettingOption, setSettingOption,
} = getActions(); } = getActions();
const suggestedLanguage = getSuggestedLanguage(); const suggestedLanguage = getSuggestedLanguage();
const lang = useOldLang(); const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const qrCodeRef = useRef<HTMLDivElement>(null); const qrCodeRef = useRef<HTMLDivElement>(null);
const isConnected = connectionState === 'connectionStateReady'; const isConnected = connectionState === 'connectionStateReady';
const continueText = useOldLangString(isConnected ? suggestedLanguage : undefined, 'ContinueOnThisLanguage', true); const continueText = useLangString('AuthContinueOnThisLanguage', suggestedLanguage);
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag(); const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag(); const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag();
@ -173,14 +171,14 @@ const AuthCode: FC<StateProps> = ({
</div> </div>
{!isQrMounted && <div className="qr-loading"><Loading /></div>} {!isQrMounted && <div className="qr-loading"><Loading /></div>}
</div> </div>
<h1>{lang('Login.QR.Title')}</h1> <h1>{lang('LoginQRTitle')}</h1>
<ol> <ol>
<li><span>{lang('Login.QR.Help1')}</span></li> <li><span>{lang('LoginQRHelp1')}</span></li>
<li><span>{renderText(lang('Login.QR2.Help2'), ['simple_markdown'])}</span></li> <li><span>{lang('LoginQRHelp2', undefined, { withNodes: true, withMarkdown: true })}</span></li>
<li><span>{lang('Login.QR.Help3')}</span></li> <li><span>{lang('LoginQRHelp3')}</span></li>
</ol> </ol>
{isAuthReady && ( {isAuthReady && (
<Button isText onClick={habdleReturnToAuthPhoneNumber}>{lang('Login.QR.Cancel')}</Button> <Button isText onClick={habdleReturnToAuthPhoneNumber}>{lang('LoginQRCancel')}</Button>
)} )}
{suggestedLanguage && suggestedLanguage !== language && continueText && ( {suggestedLanguage && suggestedLanguage !== language && continueText && (
<Button isText isLoading={isLoading} onClick={handleLangChange}>{continueText}</Button> <Button isText isLoading={isLoading} onClick={handleLangChange}>{continueText}</Button>

View File

@ -7,35 +7,35 @@ import type { GlobalState } from '../../global/types';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import useOldLang from '../../hooks/useOldLang'; import useLang from '../../hooks/useLang';
import AvatarEditable from '../ui/AvatarEditable'; import AvatarEditable from '../ui/AvatarEditable';
import Button from '../ui/Button'; import Button from '../ui/Button';
import InputText from '../ui/InputText'; import InputText from '../ui/InputText';
type StateProps = Pick<GlobalState, 'authIsLoading' | 'authError'>; type StateProps = Pick<GlobalState, 'authIsLoading' | 'authErrorKey'>;
const AuthRegister: FC<StateProps> = ({ const AuthRegister: FC<StateProps> = ({
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 [isButtonShown, setIsButtonShown] = useState(false);
const [croppedFile, setCroppedFile] = useState<File | undefined>(); const [croppedFile, setCroppedFile] = useState<File | undefined>();
const [firstName, setFirstName] = useState(''); const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState(''); const [lastName, setLastName] = useState('');
const handleFirstNameChange = useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleFirstNameChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
if (authError) { if (authErrorKey) {
clearAuthError(); clearAuthErrorKey();
} }
const { target } = event; const { target } = event;
setFirstName(target.value); setFirstName(target.value);
setIsButtonShown(target.value.length > 0); setIsButtonShown(target.value.length > 0);
}, [authError, clearAuthError]); }, [authErrorKey]);
const handleLastNameChange = useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleLastNameChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const { target } = event; const { target } = event;
@ -59,18 +59,18 @@ const AuthRegister: FC<StateProps> = ({
<form action="" method="post" onSubmit={handleSubmit}> <form action="" method="post" onSubmit={handleSubmit}>
<AvatarEditable onChange={setCroppedFile} /> <AvatarEditable onChange={setCroppedFile} />
<h2>{lang('YourName')}</h2> <h2>{lang('YourName')}</h2>
<p className="note">{lang('Login.Register.Desc')}</p> <p className="note">{lang('LoginRegisterDesc')}</p>
<InputText <InputText
id="registration-first-name" id="registration-first-name"
label={lang('Login.Register.FirstName.Placeholder')} label={lang('LoginRegisterFirstNamePlaceholder')}
onChange={handleFirstNameChange} onChange={handleFirstNameChange}
value={firstName} value={firstName}
error={authError && lang(authError)} error={authErrorKey && lang.withRegular(authErrorKey)}
autoComplete="given-name" autoComplete="given-name"
/> />
<InputText <InputText
id="registration-last-name" id="registration-last-name"
label={lang('Login.Register.LastName.Placeholder')} label={lang('LoginRegisterLastNamePlaceholder')}
onChange={handleLastNameChange} onChange={handleLastNameChange}
value={lastName} value={lastName}
autoComplete="family-name" autoComplete="family-name"
@ -85,5 +85,5 @@ const AuthRegister: FC<StateProps> = ({
}; };
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => pick(global, ['authIsLoading', 'authError']), (global): StateProps => pick(global, ['authIsLoading', 'authErrorKey']),
)(AuthRegister)); )(AuthRegister));

View File

@ -12,7 +12,7 @@ import { isoToEmoji } from '../../util/emoji/emoji';
import { prepareSearchWordsForNeedle } from '../../util/searchWords'; import { prepareSearchWordsForNeedle } from '../../util/searchWords';
import renderText from '../common/helpers/renderText'; import renderText from '../common/helpers/renderText';
import useOldLang from '../../hooks/useOldLang'; import useLang from '../../hooks/useLang';
import useSyncEffect from '../../hooks/useSyncEffect'; import useSyncEffect from '../../hooks/useSyncEffect';
import DropdownMenu from '../ui/DropdownMenu'; import DropdownMenu from '../ui/DropdownMenu';
@ -42,7 +42,7 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
onChange, onChange,
phoneCodeList, phoneCodeList,
}) => { }) => {
const lang = useOldLang(); const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@ -120,7 +120,7 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
onInput={handleCodeInput} onInput={handleCodeInput}
onKeyDown={handleInputKeyDown} onKeyDown={handleInputKeyDown}
/> />
<label>{lang('Login.SelectCountry.Title')}</label> <label>{lang('LoginSelectCountryTitle')}</label>
{isLoading ? ( {isLoading ? (
<Spinner color="black" /> <Spinner color="black" />
) : ( ) : (
@ -154,7 +154,7 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
className="no-results" className="no-results"
disabled disabled
> >
<span>{lang('lng_country_none')}</span> <span>{lang('CountryNone')}</span>
</MenuItem> </MenuItem>
)} )}
</DropdownMenu> </DropdownMenu>

View File

@ -8,7 +8,7 @@ import { AUTODOWNLOAD_FILESIZE_MB_LIMITS } from '../../../config';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useLang from '../../../hooks/useLang';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import RangeSlider from '../../ui/RangeSlider'; import RangeSlider from '../../ui/RangeSlider';
@ -53,7 +53,7 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
}) => { }) => {
const { setSettingOption } = getActions(); const { setSettingOption } = getActions();
const lang = useOldLang(); const lang = useLang();
useHistoryBack({ useHistoryBack({
isActive, isActive,
@ -61,7 +61,9 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
}); });
const renderFileSizeCallback = useCallback((value: number) => { 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]); }, [lang]);
const handleFileSizeChange = useCallback((value: number) => { const handleFileSizeChange = useCallback((value: number) => {
@ -98,26 +100,26 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{title}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{title}</h4>
<Checkbox <Checkbox
label={lang('AutoDownloadSettings.Contacts')} label={lang('AutoDownloadSettingsContacts')}
checked={canAutoLoadFromContacts} checked={canAutoLoadFromContacts}
// TODO rewrite to support `useCallback` // TODO rewrite to support `useCallback`
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })} onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })}
/> />
<Checkbox <Checkbox
label={lang('AutoDownloadSettings.PrivateChats')} label={lang('AutoDownloadSettingsPrivateChats')}
checked={canAutoLoadInPrivateChats} checked={canAutoLoadInPrivateChats}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })} onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })}
/> />
<Checkbox <Checkbox
label={lang('AutoDownloadSettings.GroupChats')} label={lang('AutoDownloadSettingsGroupChats')}
checked={canAutoLoadInGroups} checked={canAutoLoadInGroups}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })} onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })}
/> />
<Checkbox <Checkbox
label={lang('AutoDownloadSettings.Channels')} label={lang('AutoDownloadSettingsChannels')}
checked={canAutoLoadInChannels} checked={canAutoLoadInChannels}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })} onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })}
@ -147,7 +149,7 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
canAutoLoadVideoInChannels, canAutoLoadVideoInChannels,
)} )}
{renderAutoDownloadBlock( {renderAutoDownloadBlock(
'Auto-download files', // Proper translation is not available yet lang('AutoDownloadFilesTitle'),
'File', 'File',
canAutoLoadFileFromContacts, canAutoLoadFileFromContacts,
canAutoLoadFileInPrivateChats, canAutoLoadFileInPrivateChats,

View File

@ -17,7 +17,7 @@ import {
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useLang from '../../../hooks/useLang';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -41,14 +41,6 @@ type StateProps =
shouldUseSystemTheme: boolean; shouldUseSystemTheme: boolean;
}; };
const TIME_FORMAT_OPTIONS: IRadioOption[] = [{
label: '12-hour',
value: '12h',
}, {
label: '24-hour',
value: '24h',
}];
const SettingsGeneral: FC<OwnProps & StateProps> = ({ const SettingsGeneral: FC<OwnProps & StateProps> = ({
isActive, isActive,
onScreenSelect, onScreenSelect,
@ -63,28 +55,36 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
setSettingOption, setSettingOption,
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useLang();
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const isMobileDevice = isMobile && (IS_IOS || IS_ANDROID); const isMobileDevice = isMobile && (IS_IOS || IS_ANDROID);
const timeFormatOptions: IRadioOption[] = [{
label: lang('SettingsTimeFormat12'),
value: '12h',
}, {
label: lang('SettingsTimeFormat24'),
value: '24h',
}];
const appearanceThemeOptions: IRadioOption[] = [{ const appearanceThemeOptions: IRadioOption[] = [{
label: lang('EmptyChat.Appearance.Light'), label: lang('EmptyChatAppearanceLight'),
value: 'light', value: 'light',
}, { }, {
label: lang('EmptyChat.Appearance.Dark'), label: lang('EmptyChatAppearanceDark'),
value: 'dark', value: 'dark',
}, { }, {
label: lang('EmptyChat.Appearance.System'), label: lang('EmptyChatAppearanceSystem'),
value: 'auto', value: 'auto',
}]; }];
const keyboardSendOptions = !isMobileDevice ? [ 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', value: 'ctrl-enter',
label: lang(IS_MAC_OS || IS_IOS ? 'lng_settings_send_cmdenter' : 'lng_settings_send_ctrlenter'), label: lang(IS_MAC_OS || IS_IOS ? 'SettingsSendCmdenter' : 'SettingsSendCtrlenter'),
subLabel: 'New line by Enter', subLabel: lang('SettingsSendPlusEnterDescription'),
}, },
] : undefined; ] : undefined;
@ -134,7 +134,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item pt-3"> <div className="settings-item pt-3">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('SETTINGS')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('Settings')}</h4>
<RangeSlider <RangeSlider
label={lang('TextSize')} label={lang('TextSize')}
@ -155,7 +155,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
{IS_ELECTRON && IS_WINDOWS && ( {IS_ELECTRON && IS_WINDOWS && (
<Checkbox <Checkbox
label={lang('GeneralSettings.StatusBarItem')} label={lang('SettingsTray')}
checked={Boolean(isTrayIconEnabled)} checked={Boolean(isTrayIconEnabled)}
onCheck={handleIsTrayIconEnabledChange} onCheck={handleIsTrayIconEnabledChange}
/> />
@ -176,11 +176,11 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
Time Format {lang('SettingsTimeFormat')}
</h4> </h4>
<RadioGroup <RadioGroup
name="timeformat" name="timeformat"
options={TIME_FORMAT_OPTIONS} options={timeFormatOptions}
selected={timeFormat} selected={timeFormat}
onChange={handleTimeFormatChange} onChange={handleTimeFormatChange}
/> />
@ -188,7 +188,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
{keyboardSendOptions && ( {keyboardSendOptions && (
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('VoiceOver.Keyboard')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('SettingsKeyboard')}</h4>
<RadioGroup <RadioGroup
name="keyboard-send-settings" name="keyboard-send-settings"

View File

@ -163,7 +163,7 @@ const SettingsHeader: FC<OwnProps> = ({
return <h3>{oldLang('NeverAllow')}</h3>; return <h3>{oldLang('NeverAllow')}</h3>;
case SettingsScreens.Performance: case SettingsScreens.Performance:
return <h3>{oldLang('Animations and Performance')}</h3>; return <h3>{lang('MenuAnimations')}</h3>;
case SettingsScreens.ActiveSessions: case SettingsScreens.ActiveSessions:
return <h3>{oldLang('SessionsTitle')}</h3>; return <h3>{oldLang('SessionsTitle')}</h3>;

View File

@ -16,7 +16,6 @@ import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import StarIcon from '../../common/icons/StarIcon'; import StarIcon from '../../common/icons/StarIcon';
import ChatExtra from '../../common/profile/ChatExtra'; import ChatExtra from '../../common/profile/ChatExtra';
@ -62,7 +61,6 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
const [isSupportDialogOpen, openSupportDialog, closeSupportDialog] = useFlag(false); const [isSupportDialogOpen, openSupportDialog, closeSupportDialog] = useFlag(false);
const lang = useLang(); const lang = useLang();
const oldLang = useOldLang();
useEffect(() => { useEffect(() => {
if (currentUserId) { if (currentUserId) {
@ -104,7 +102,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.General)} onClick={() => onScreenSelect(SettingsScreens.General)}
> >
{oldLang('Telegram.GeneralSettingsViewController')} {lang('TelegramGeneralSettingsViewController')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="animations" icon="animations"
@ -112,7 +110,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Performance)} onClick={() => onScreenSelect(SettingsScreens.Performance)}
> >
{oldLang('Animations and Performance')} {lang('MenuAnimations')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="unmute" icon="unmute"
@ -120,7 +118,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Notifications)} onClick={() => onScreenSelect(SettingsScreens.Notifications)}
> >
{oldLang('Notifications')} {lang('Notifications')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="data" icon="data"
@ -128,7 +126,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.DataStorage)} onClick={() => onScreenSelect(SettingsScreens.DataStorage)}
> >
{oldLang('DataSettings')} {lang('DataSettings')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="lock" icon="lock"
@ -136,7 +134,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Privacy)} onClick={() => onScreenSelect(SettingsScreens.Privacy)}
> >
{oldLang('PrivacySettings')} {lang('PrivacySettings')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="folder" icon="folder"
@ -144,7 +142,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Folders)} onClick={() => onScreenSelect(SettingsScreens.Folders)}
> >
{oldLang('Filters')} {lang('Filters')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="active-sessions" icon="active-sessions"
@ -152,7 +150,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.ActiveSessions)} onClick={() => onScreenSelect(SettingsScreens.ActiveSessions)}
> >
{oldLang('SessionsTitle')} {lang('SessionsTitle')}
{sessionCount > 0 && (<span className="settings-item__current-value">{sessionCount}</span>)} {sessionCount > 0 && (<span className="settings-item__current-value">{sessionCount}</span>)}
</ListItem> </ListItem>
<ListItem <ListItem
@ -161,8 +159,8 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Language)} onClick={() => onScreenSelect(SettingsScreens.Language)}
> >
{oldLang('Language')} {lang('Language')}
<span className="settings-item__current-value">{oldLang.langName}</span> <span className="settings-item__current-value">{lang.languageInfo.nativeName}</span>
</ListItem> </ListItem>
<ListItem <ListItem
icon="stickers" icon="stickers"
@ -170,7 +168,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Stickers)} onClick={() => onScreenSelect(SettingsScreens.Stickers)}
> >
{oldLang('StickersName')} {lang('MenuStickers')}
</ListItem> </ListItem>
</div> </div>
<div className="settings-main-menu"> <div className="settings-main-menu">
@ -181,7 +179,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => openPremiumModal()} onClick={() => openPremiumModal()}
> >
{oldLang('TelegramPremium')} {lang('TelegramPremium')}
</ListItem> </ListItem>
)} )}
{shouldDisplayStars && ( {shouldDisplayStars && (
@ -191,7 +189,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => openStarsBalanceModal({})} onClick={() => openStarsBalanceModal({})}
> >
{oldLang('MenuTelegramStars')} {lang('MenuStars')}
{Boolean(starsBalance) && ( {Boolean(starsBalance) && (
<span className="settings-item__current-value"> <span className="settings-item__current-value">
{formatStarsAmount(lang, starsBalance)} {formatStarsAmount(lang, starsBalance)}
@ -206,7 +204,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => openGiftRecipientPicker()} onClick={() => openGiftRecipientPicker()}
> >
{oldLang('SendAGift')} {lang('MenuSendGift')}
</ListItem> </ListItem>
)} )}
</div> </div>
@ -216,7 +214,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
narrow narrow
onClick={openSupportDialog} onClick={openSupportDialog}
> >
{oldLang('AskAQuestion')} {lang('AskAQuestion')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="help" icon="help"
@ -224,7 +222,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => openUrl({ url: FAQ_URL })} onClick={() => openUrl({ url: FAQ_URL })}
> >
{oldLang('TelegramFaq')} {lang('MenuTelegramFaq')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="privacy-policy" icon="privacy-policy"
@ -232,14 +230,14 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => openUrl({ url: PRIVACY_URL })} onClick={() => openUrl({ url: PRIVACY_URL })}
> >
{oldLang('PrivacyPolicy')} {lang('MenuPrivacyPolicy')}
</ListItem> </ListItem>
</div> </div>
<ConfirmDialog <ConfirmDialog
isOpen={isSupportDialogOpen} isOpen={isSupportDialogOpen}
confirmLabel={oldLang('lng_settings_ask_ok')} confirmLabel={lang('OK')}
title={oldLang('AskAQuestion')} title={lang('AskAQuestion')}
text={oldLang('lng_settings_ask_sure')} textParts={lang('MenuAskText', undefined, { withNodes: true, renderTextFilters: ['br'] })}
confirmHandler={handleOpenSupport} confirmHandler={handleOpenSupport}
onClose={closeSupportDialog} onClose={closeSupportDialog}
/> />

View File

@ -10,7 +10,7 @@ import {
} from '../../../util/notifications'; } from '../../../util/notifications';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useLang from '../../../hooks/useLang';
import useRunDebounced from '../../../hooks/useRunDebounced'; import useRunDebounced from '../../../hooks/useRunDebounced';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
@ -139,7 +139,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
runDebounced(() => playNotifySound(undefined, volume)); runDebounced(() => playNotifySound(undefined, volume));
}, [runDebounced, updateWebNotificationSettings]); }, [runDebounced, updateWebNotificationSettings]);
const lang = useOldLang(); const lang = useLang();
useHistoryBack({ useHistoryBack({
isActive, isActive,
@ -150,27 +150,27 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
Web notifications {lang('NotificationsWeb')}
</h4> </h4>
<Checkbox <Checkbox
label="Web notifications" label={lang('NotificationsWeb')}
// eslint-disable-next-line max-len subLabel={lang(hasWebNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
subLabel={lang(hasWebNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasWebNotifications} checked={hasWebNotifications}
disabled={!areNotificationsSupported} disabled={!areNotificationsSupported}
onChange={handleWebNotificationsChange} onChange={handleWebNotificationsChange}
/> />
<Checkbox <Checkbox
label="Offline notifications" label={lang('NotificationsOffline')}
disabled={!hasWebNotifications || !areOfflineNotificationsSupported} disabled={!hasWebNotifications || !areOfflineNotificationsSupported}
// eslint-disable-next-line max-len subLabel={areOfflineNotificationsSupported
subLabel={areOfflineNotificationsSupported ? lang(hasPushNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled') : 'Not supported'} ? lang(hasPushNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')
: lang('SettingsOfflineNotificationUnsupported')}
checked={hasPushNotifications} checked={hasPushNotifications}
onChange={handlePushNotificationsChange} onChange={handlePushNotificationsChange}
/> />
<div className="settings-item-slider"> <div className="settings-item-slider">
<RangeSlider <RangeSlider
label="Sound" label={lang('NotificationsSound')}
min={0} min={0}
max={10} max={10}
disabled={!areNotificationsSupported} disabled={!areNotificationsSupported}
@ -186,16 +186,16 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox <Checkbox
label={lang('NotificationsForPrivateChats')} label={lang('NotificationsForPrivateChats')}
// eslint-disable-next-line max-len subLabel={lang(hasPrivateChatsNotifications
subLabel={lang(hasPrivateChatsNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')} ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasPrivateChatsNotifications} checked={hasPrivateChatsNotifications}
onChange={handlePrivateChatsNotificationsChange} onChange={handlePrivateChatsNotificationsChange}
/> />
<Checkbox <Checkbox
label={lang('MessagePreview')} label={lang('MessagePreview')}
disabled={!hasPrivateChatsNotifications} disabled={!hasPrivateChatsNotifications}
// eslint-disable-next-line max-len subLabel={lang(hasPrivateChatsMessagePreview
subLabel={lang(hasPrivateChatsMessagePreview ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')} ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasPrivateChatsMessagePreview} checked={hasPrivateChatsMessagePreview}
onChange={handlePrivateChatsPreviewChange} onChange={handlePrivateChatsPreviewChange}
/> />
@ -206,14 +206,14 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox <Checkbox
label={lang('NotificationsForGroups')} label={lang('NotificationsForGroups')}
subLabel={lang(hasGroupNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')} subLabel={lang(hasGroupNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasGroupNotifications} checked={hasGroupNotifications}
onChange={handleGroupsNotificationsChange} onChange={handleGroupsNotificationsChange}
/> />
<Checkbox <Checkbox
label={lang('MessagePreview')} label={lang('MessagePreview')}
disabled={!hasGroupNotifications} disabled={!hasGroupNotifications}
subLabel={lang(hasGroupMessagePreview ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')} subLabel={lang(hasGroupMessagePreview ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
checked={hasGroupMessagePreview} checked={hasGroupMessagePreview}
onChange={handleGroupsPreviewChange} onChange={handleGroupsPreviewChange}
/> />
@ -224,16 +224,14 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
<Checkbox <Checkbox
label={lang('NotificationsForChannels')} label={lang('NotificationsForChannels')}
// eslint-disable-next-line max-len subLabel={lang(hasBroadcastNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
subLabel={lang(hasBroadcastNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasBroadcastNotifications} checked={hasBroadcastNotifications}
onChange={handleChannelsNotificationsChange} onChange={handleChannelsNotificationsChange}
/> />
<Checkbox <Checkbox
label={lang('MessagePreview')} label={lang('MessagePreview')}
disabled={!hasBroadcastNotifications} disabled={!hasBroadcastNotifications}
// eslint-disable-next-line max-len subLabel={lang(hasBroadcastMessagePreview ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
subLabel={lang(hasBroadcastMessagePreview ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasBroadcastMessagePreview} checked={hasBroadcastMessagePreview}
onChange={handleChannelsPreviewChange} onChange={handleChannelsPreviewChange}
/> />

View File

@ -4,6 +4,7 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { AnimationLevel, PerformanceType, PerformanceTypeKey } from '../../../types'; import type { AnimationLevel, PerformanceType, PerformanceTypeKey } from '../../../types';
import type { RegularLangKey } from '../../../types/language';
import { import {
ANIMATION_LEVEL_CUSTOM, ANIMATION_LEVEL_MAX, ANIMATION_LEVEL_MED, ANIMATION_LEVEL_MIN, 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 { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../util/windowEnvironment';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useLang from '../../../hooks/useLang';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import RangeSlider from '../../ui/RangeSlider'; import RangeSlider from '../../ui/RangeSlider';
type PerformanceSection = [string, PerformanceOption[]]; type PerformanceSection = [RegularLangKey, PerformanceOption[]];
type PerformanceOption = { type PerformanceOption = {
key: PerformanceTypeKey; key: PerformanceTypeKey;
label: string; label: RegularLangKey;
disabled?: boolean; disabled?: boolean;
}; };
@ -39,38 +40,38 @@ type StateProps = {
performanceSettings: PerformanceType; performanceSettings: PerformanceType;
}; };
const ANIMATION_LEVEL_OPTIONS = [ const ANIMATION_LEVEL_OPTIONS: RegularLangKey[] = [
'Power Saving', 'SettingsPerformanceSliderLow',
'Nice and Fast', 'SettingsPerformanceSliderMedium',
'Lots of Stuff', 'SettingsPerformanceSliderHigh',
]; ];
const ANIMATION_LEVEL_CUSTOM_OPTIONS = [ const ANIMATION_LEVEL_CUSTOM_OPTIONS: RegularLangKey[] = [
'Power Saving', 'SettingsPerformanceSliderLow',
'Custom', 'SettingsPerformanceSliderCustom',
'Lots of Stuff', 'SettingsPerformanceSliderHigh',
]; ];
const PERFORMANCE_OPTIONS: PerformanceSection[] = [ const PERFORMANCE_OPTIONS: PerformanceSection[] = [
['LiteMode.Key.animations.Title', [ ['SettingsPerformanceInterfaceAnimations', [
{ key: 'pageTransitions', label: 'Page Transitions' }, { key: 'pageTransitions', label: 'SettingsPerformancePageTransitions' },
{ key: 'messageSendingAnimations', label: 'Message Sending Animation' }, { key: 'messageSendingAnimations', label: 'SettingsPerformanceSending' },
{ key: 'mediaViewerAnimations', label: 'Media Viewer Animations' }, { key: 'mediaViewerAnimations', label: 'SettingsPerformanceMediaViewer' },
{ key: 'messageComposerAnimations', label: 'Message Composer Animations' }, { key: 'messageComposerAnimations', label: 'SettingsPerformanceComposer' },
{ key: 'contextMenuAnimations', label: 'Context Menu Animation' }, { key: 'contextMenuAnimations', label: 'SettingsPerformanceContextAnimation' },
{ key: 'contextMenuBlur', label: 'Context Menu Blur', disabled: !IS_BACKDROP_BLUR_SUPPORTED }, { key: 'contextMenuBlur', label: 'SettingsPerformanceContextBlur', disabled: !IS_BACKDROP_BLUR_SUPPORTED },
{ key: 'rightColumnAnimations', label: 'Right Column Animation' }, { key: 'rightColumnAnimations', label: 'SettingsPerformanceRightColumn' },
{ key: 'snapEffect', label: 'Dust-effect deletion' }, { key: 'snapEffect', label: 'SettingsPerformanceThanos' },
]], ]],
['Stickers and Emoji', [ ['SettingsPerformanceStickers', [
{ key: 'animatedEmoji', label: 'Allow Animated Emoji' }, { key: 'animatedEmoji', label: 'SettingsPerformanceAnimatedEmoji' },
{ key: 'loopAnimatedStickers', label: 'Loop Animated Stickers' }, { key: 'loopAnimatedStickers', label: 'SettingsPerformanceLoopStickers' },
{ key: 'reactionEffects', label: 'Reaction Effects' }, { key: 'reactionEffects', label: 'SettingsPerformanceReactionEffects' },
{ key: 'stickerEffects', label: 'Full-Screen Sticker and Emoji Effects' }, { key: 'stickerEffects', label: 'SettingsPerformanceStickerEffects' },
]], ]],
['AutoplayMedia', [ ['SettingsPerformanceMediaAutoplay', [
{ key: 'autoplayGifs', label: 'AutoplayGIF' }, { key: 'autoplayGifs', label: 'SettingsPerformanceAutoplayGif' },
{ key: 'autoplayVideos', label: 'AutoplayVideo' }, { key: 'autoplayVideos', label: 'SettingsPerformanceAutoplayVideo' },
]], ]],
]; ];
@ -89,7 +90,7 @@ function SettingsPerformance({
onBack: onReset, onBack: onReset,
}); });
const lang = useOldLang(); const lang = useLang();
const [sectionExpandedStates, setSectionExpandedStates] = useState<Record<number, boolean>>({}); const [sectionExpandedStates, setSectionExpandedStates] = useState<Record<number, boolean>>({});
const sectionCheckedStates = useMemo(() => { const sectionCheckedStates = useMemo(() => {
@ -113,9 +114,14 @@ function SettingsPerformance({
return ANIMATION_LEVEL_CUSTOM; return ANIMATION_LEVEL_CUSTOM;
}, [performanceSettings]); }, [performanceSettings]);
const animationLevelOptions = animationLevelState === ANIMATION_LEVEL_CUSTOM
? ANIMATION_LEVEL_CUSTOM_OPTIONS const animationLevelOptions = useMemo(() => {
: ANIMATION_LEVEL_OPTIONS; 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) => { const handleToggleSection = useCallback((e: React.MouseEvent, index?: string) => {
e.preventDefault(); e.preventDefault();
@ -161,10 +167,10 @@ function SettingsPerformance({
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
Animation Level {lang('SettingsPerformanceSliderTitle')}
</h4> </h4>
<p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}> <p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
Choose the desired animations amount. {lang('SettingsPerformanceSliderSubtitle')}
</p> </p>
<RangeSlider <RangeSlider

View File

@ -323,7 +323,6 @@ export const MAX_MEDIA_FILES_FOR_ALBUM = 10;
export const MAX_ACTIVE_PINNED_CHATS = 5; export const MAX_ACTIVE_PINNED_CHATS = 5;
export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE; export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE;
export const DEFAULT_LANG_CODE = 'en'; export const DEFAULT_LANG_CODE = 'en';
export const OLD_DEFAULT_LANG_PACK = 'android';
export const LANG_PACKS = ['android', 'ios', 'tdesktop', 'macos'] as const; export const LANG_PACKS = ['android', 'ios', 'tdesktop', 'macos'] as const;
export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time'; export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
export const FAQ_URL = 'https://telegram.org/faq'; export const FAQ_URL = 'https://telegram.org/faq';

View File

@ -69,7 +69,7 @@ addActionHandler('setAuthPhoneNumber', (global, actions, payload): ActionReturnT
return { return {
...global, ...global,
authIsLoading: true, authIsLoading: true,
authError: undefined, authErrorKey: undefined,
}; };
}); });
@ -81,7 +81,7 @@ addActionHandler('setAuthCode', (global, actions, payload): ActionReturnType =>
return { return {
...global, ...global,
authIsLoading: true, authIsLoading: true,
authError: undefined, authErrorKey: undefined,
}; };
}); });
@ -93,7 +93,7 @@ addActionHandler('setAuthPassword', (global, actions, payload): ActionReturnType
return { return {
...global, ...global,
authIsLoading: true, authIsLoading: true,
authError: undefined, authErrorKey: undefined,
}; };
}); });
@ -124,7 +124,7 @@ addActionHandler('signUp', (global, actions, payload): ActionReturnType => {
return { return {
...global, ...global,
authIsLoading: true, authIsLoading: true,
authError: undefined, authErrorKey: undefined,
}; };
}); });
@ -133,7 +133,7 @@ addActionHandler('returnToAuthPhoneNumber', (global): ActionReturnType => {
return { return {
...global, ...global,
authError: undefined, authErrorKey: undefined,
}; };
}); });
@ -143,7 +143,7 @@ addActionHandler('goToAuthQrCode', (global): ActionReturnType => {
return { return {
...global, ...global,
authIsLoadingQrCode: true, authIsLoadingQrCode: true,
authError: undefined, authErrorKey: undefined,
}; };
}); });

View File

@ -180,10 +180,9 @@ function onUpdateAuthorizationState<T extends GlobalState>(global: T, update: Ap
} }
function onUpdateAuthorizationError<T extends GlobalState>(global: T, update: ApiUpdateAuthorizationError) { function onUpdateAuthorizationError<T extends GlobalState>(global: T, update: ApiUpdateAuthorizationError) {
global = getGlobal();
global = { global = {
...global, ...global,
authError: update.message, authErrorKey: update.errorKey,
}; };
setGlobal(global); setGlobal(global);
} }

View File

@ -217,10 +217,10 @@ addActionHandler('setAuthRememberMe', (global, actions, payload): ActionReturnTy
}; };
}); });
addActionHandler('clearAuthError', (global): ActionReturnType => { addActionHandler('clearAuthErrorKey', (global): ActionReturnType => {
return { return {
...global, ...global,
authError: undefined, authErrorKey: undefined,
}; };
}); });

View File

@ -116,7 +116,7 @@ export interface ActionPayloads {
}; };
returnToAuthPhoneNumber: undefined; returnToAuthPhoneNumber: undefined;
setAuthRememberMe: boolean; setAuthRememberMe: boolean;
clearAuthError: undefined; clearAuthErrorKey: undefined;
uploadProfilePhoto: { uploadProfilePhoto: {
file: File; file: File;
isFallback?: boolean; isFallback?: boolean;

View File

@ -68,6 +68,7 @@ import type {
TopicsInfo, TopicsInfo,
WebPageMediaSize, WebPageMediaSize,
} from '../../types'; } from '../../types';
import type { RegularLangFnParameters } from '../../util/localization';
import type { TabState } from './tabState'; import type { TabState } from './tabState';
export type GlobalState = { export type GlobalState = {
@ -145,7 +146,7 @@ export type GlobalState = {
authPhoneNumber?: string; authPhoneNumber?: string;
authIsLoading?: boolean; authIsLoading?: boolean;
authIsLoadingQrCode?: boolean; authIsLoadingQrCode?: boolean;
authError?: string; authErrorKey?: RegularLangFnParameters;
authRememberMe?: boolean; authRememberMe?: boolean;
authNearestCountry?: string; authNearestCountry?: string;
authIsCodeViaApp?: boolean; authIsCodeViaApp?: boolean;

View File

@ -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<string | undefined>(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;
}

View File

@ -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;

View File

@ -142,7 +142,7 @@ export interface LangPair {
'LoginQRLogin': undefined; 'LoginQRLogin': undefined;
'LoginQRTitle': undefined; 'LoginQRTitle': undefined;
'LoginQRHelp1': undefined; 'LoginQRHelp1': undefined;
'LoginQR2Help2': undefined; 'LoginQRHelp2': undefined;
'LoginQRHelp3': undefined; 'LoginQRHelp3': undefined;
'LoginQRCancel': undefined; 'LoginQRCancel': undefined;
'YourName': undefined; 'YourName': undefined;
@ -344,6 +344,8 @@ export interface LangPair {
'AuthSessionsViewBrowser': undefined; 'AuthSessionsViewBrowser': undefined;
'AuthSessionsViewLocationInfo': undefined; 'AuthSessionsViewLocationInfo': undefined;
'AuthSessionsLogOutApplications': undefined; 'AuthSessionsLogOutApplications': undefined;
'AuthKeepSignedIn': undefined;
'AuthTitle': undefined;
'ClearOtherWebSessionsHelp': undefined; 'ClearOtherWebSessionsHelp': undefined;
'AreYouSureWebSessions': undefined; 'AreYouSureWebSessions': undefined;
'AutoDownloadMaxFileSize': undefined; 'AutoDownloadMaxFileSize': undefined;
@ -369,10 +371,17 @@ export interface LangPair {
'SettingsSendEnter': undefined; 'SettingsSendEnter': undefined;
'SettingsSendCmdenter': undefined; 'SettingsSendCmdenter': undefined;
'SettingsSendCtrlenter': undefined; 'SettingsSendCtrlenter': undefined;
'SettingsSendEnterDescription': undefined;
'SettingsSendPlusEnterDescription': undefined;
'SettingsTimeFormat': undefined;
'SettingsTimeFormat12': undefined;
'SettingsTimeFormat24': undefined;
'SettingsKeyboard': undefined;
'SettingsTray': undefined;
'SettingsOfflineNotificationUnsupported': undefined;
'TextSize': undefined; 'TextSize': undefined;
'ChatBackground': undefined; 'ChatBackground': undefined;
'Theme': undefined; 'Theme': undefined;
'VoiceOverKeyboard': undefined;
'AccDescrStickers': undefined; 'AccDescrStickers': undefined;
'DoubleTapSetting': undefined; 'DoubleTapSetting': undefined;
'SuggestStickers': undefined; 'SuggestStickers': undefined;
@ -544,10 +553,46 @@ export interface LangPair {
'PollsSolutionTitle': undefined; 'PollsSolutionTitle': undefined;
'CreatePollExplanationInfo': undefined; 'CreatePollExplanationInfo': undefined;
'VoipGroupPersonalAccount': 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; 'FavoriteStickers': undefined;
'PremiumStickers': undefined; 'PremiumStickers': undefined;
'GroupStickers': undefined; 'GroupStickers': undefined;
'ErrorSendRestrictedStickersAll': undefined; 'ErrorSendRestrictedStickersAll': undefined;
'ErrorPhoneNumberInvalid': undefined;
'ErrorCodeInvalid': undefined;
'ErrorIncorrectPassword': undefined;
'ErrorPasswordFlood': undefined;
'ErrorPhoneBanned': undefined;
'ErrorUnexpected': undefined;
'NoStickers': undefined; 'NoStickers': undefined;
'ClearRecentEmoji': undefined; 'ClearRecentEmoji': undefined;
'TextFormatAddLinkTitle': undefined; 'TextFormatAddLinkTitle': undefined;
@ -994,7 +1039,6 @@ export interface LangPair {
'SettingsAnimationsHigh': undefined; 'SettingsAnimationsHigh': undefined;
'Settings12HourFormat': undefined; 'Settings12HourFormat': undefined;
'Settings24HourFormat': undefined; 'Settings24HourFormat': undefined;
'SettingsSendEnterDescription': undefined;
'SettingsSendCtrlEnterDescription': undefined; 'SettingsSendCtrlEnterDescription': undefined;
'AriaMoreButton': undefined; 'AriaMoreButton': undefined;
'RecoveryEmailCode': undefined; 'RecoveryEmailCode': undefined;
@ -1094,7 +1138,6 @@ export interface LangPair {
'HideCaption': undefined; 'HideCaption': undefined;
'ChangeRecipient': undefined; 'ChangeRecipient': undefined;
'DragToSortAria': undefined; 'DragToSortAria': undefined;
'SettingsTimeFormat': undefined;
'MenuReportBug': undefined; 'MenuReportBug': undefined;
'MenuBetaChangelog': undefined; 'MenuBetaChangelog': undefined;
'MenuSwitchToK': undefined; 'MenuSwitchToK': undefined;
@ -1239,6 +1282,7 @@ export interface LangPair {
'ViewButtonBoost': undefined; 'ViewButtonBoost': undefined;
'ViewButtonStickerset': undefined; 'ViewButtonStickerset': undefined;
'ViewButtonGiftUnique': undefined; 'ViewButtonGiftUnique': undefined;
'AuthContinueOnThisLanguage': undefined;
} }
export interface LangPairWithVariables<V extends unknown = LangVariable> { export interface LangPairWithVariables<V extends unknown = LangVariable> {
@ -1404,6 +1448,9 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
'SlowModeHint': { 'SlowModeHint': {
'time': V; 'time': V;
}; };
'ErrorUnexpectedMessage': {
'error': V;
};
'EditedDate': { 'EditedDate': {
'date': V; 'date': V;
}; };
@ -1761,6 +1808,9 @@ export interface LangPairPluralWithVariables<V extends unknown = LangVariable> {
'PreviewSenderSendFile': { 'PreviewSenderSendFile': {
'count': V; 'count': V;
}; };
'ErrorFlood': {
'hour': V;
};
'PinnedMessageTitle': { 'PinnedMessageTitle': {
'index': V; 'index': V;
}; };

View File

@ -1,7 +1,7 @@
import { DEBUG } from '../../config'; import { DEBUG } from '../../config';
export default function readStrings(data: string): Record<string, string> { export default function readStrings(data: string): Record<string, string> {
const lines = data.split(/;?\r?\n/); const lines = data.split(/;\r?\n?/);
const result: Record<string, string> = {}; const result: Record<string, string> = {};
for (const line of lines) { for (const line of lines) {
if (!line.startsWith('"')) continue; if (!line.startsWith('"')) continue;

View File

@ -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;

View File

@ -320,10 +320,17 @@ function createTranslationFn(): LangFn {
} }
return processTranslation(key, variables as Record<string, LangVariable>, options); return processTranslation(key, variables as Record<string, LangVariable>, 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.region = (code: string) => formatters?.region.of(code);
fn.conjunction = (list: string[]) => formatters?.conjunction.format(list) || list.join(', '); fn.conjunction = (list: string[]) => formatters?.conjunction.format(list) || list.join(', ');
fn.disjunction = (list: string[]) => formatters?.disjunction.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.number = (value: number) => formatters?.number.format(value) || String(value);
fn.languageInfo = language!;
return fn; return fn;
} }

View File

@ -1,6 +1,7 @@
import type { TeactNode } from '../../lib/teact/teact'; import type { TeactNode } from '../../lib/teact/teact';
import type { import type {
ApiLanguage,
LangPackStringValue, LangPackStringValue,
LangPackStringValueDeleted, LangPackStringValueDeleted,
LangPackStringValuePlural, LangPackStringValuePlural,
@ -143,6 +144,8 @@ export type LangFn = {
): TeactNode; ): TeactNode;
with: (params: LangFnParameters) => TeactNode; with: (params: LangFnParameters) => TeactNode;
withRegular: (params: RegularLangFnParameters) => string;
withAdvanced: (params: AdvancedLangFnParameters) => TeactNode;
region: (code: string) => string | undefined; region: (code: string) => string | undefined;
conjunction: (list: string[]) => string; conjunction: (list: string[]) => string;
disjunction: (list: string[]) => string; disjunction: (list: string[]) => string;
@ -150,6 +153,7 @@ export type LangFn = {
isRtl?: boolean; isRtl?: boolean;
code: string; code: string;
pluralCode: string; pluralCode: string;
languageInfo: ApiLanguage;
}; };
type ListFormat = Pick<Intl.ListFormat, 'format'>; type ListFormat = Pick<Intl.ListFormat, 'format'>;

View File

@ -4,12 +4,11 @@ import type { ApiOldLangPack, ApiOldLangString } from '../api/types';
import type { LangCode, TimeFormat } from '../types'; import type { LangCode, TimeFormat } from '../types';
import { import {
DEFAULT_LANG_CODE, LANG_CACHE_NAME, LANG_PACKS, OLD_DEFAULT_LANG_PACK, DEFAULT_LANG_CODE, LANG_CACHE_NAME, LANG_PACKS,
} from '../config'; } from '../config';
import { callApi } from '../api/gramjs'; import { callApi } from '../api/gramjs';
import * as cacheApi from './cacheApi'; import * as cacheApi from './cacheApi';
import { createCallbackManager } from './callbacks'; import { createCallbackManager } from './callbacks';
import { fallbackLangPackInitial } from './fallbackLangPackInitial';
import { loadAndChangeLanguage } from './localization'; import { loadAndChangeLanguage } from './localization';
import { formatInteger } from './textFormat'; import { formatInteger } from './textFormat';
@ -129,7 +128,7 @@ function createLangFn() {
void importFallbackLangPack(); void importFallbackLangPack();
} }
const langString = langPack?.[key] || fallbackLangPack?.[key] || fallbackLangPackInitial[key]; const langString = langPack?.[key] || fallbackLangPack?.[key];
if (!langString) { if (!langString) {
return key; return key;
} }
@ -151,23 +150,6 @@ export function getTranslationFn(): LangFn {
return translationFn; 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 * @deprecated Migrate to `changeLanguage` in `util/localization.ts` instead
*/ */
@ -246,28 +228,6 @@ async function fetchRemote(langCode: string): Promise<ApiOldLangPack | undefined
return undefined; return undefined;
} }
async function fetchRemoteString(
remoteLangPack: typeof LANG_PACKS[number], langCode: string, key: string,
): Promise<ApiOldLangString | undefined> {
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) { function getPluralOption(amount: number) {
const langCode = currentLangCode || DEFAULT_LANG_CODE; const langCode = currentLangCode || DEFAULT_LANG_CODE;
const optionIndex = PLURAL_RULES[langCode as keyof typeof PLURAL_RULES] const optionIndex = PLURAL_RULES[langCode as keyof typeof PLURAL_RULES]