diff --git a/src/api/gramjs/methods/auth.ts b/src/api/gramjs/methods/auth.ts index 09f32d42d..69060fd2b 100644 --- a/src/api/gramjs/methods/auth.ts +++ b/src/api/gramjs/methods/auth.ts @@ -9,9 +9,9 @@ import { import { DEBUG } from '../../../config'; const ApiErrors: { [k: string]: string } = { - PHONE_NUMBER_INVALID: 'Invalid Phone Number', - PHONE_CODE_INVALID: 'Invalid Code', - PASSWORD_HASH_INVALID: 'Invalid Password', + PHONE_NUMBER_INVALID: 'PHONE_NUMBER_INVALID', + PHONE_CODE_INVALID: 'PHONE_CODE_INVALID', + PASSWORD_HASH_INVALID: 'PASSWORD_HASH_INVALID', PHONE_PASSWORD_FLOOD: 'You have tried logging in too many times', }; diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index a8db1f6a0..31ead19b8 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -47,7 +47,7 @@ export { fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations, fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings, fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice, - updateIsOnline, fetchContentSettings, updateContentSettings, + updateIsOnline, fetchContentSettings, updateContentSettings, fetchLangStrings, } from './settings'; export { diff --git a/src/api/gramjs/methods/settings.ts b/src/api/gramjs/methods/settings.ts index 020ed853c..271ac19f0 100644 --- a/src/api/gramjs/methods/settings.ts +++ b/src/api/gramjs/methods/settings.ts @@ -6,7 +6,7 @@ import { } from '../../types'; import { ApiPrivacyKey, IInputPrivacyRules } from '../../../types'; -import { BLOCKED_LIST_LIMIT, DEFAULT_LANG_PACK } from '../../../config'; +import { BLOCKED_LIST_LIMIT, DEFAULT_LANG_PACK, LANG_PACKS } from '../../../config'; import { buildApiWallpaper, buildApiSession, buildPrivacyRules, buildApiNotifyException, } from '../apiBuilders/misc'; @@ -278,7 +278,10 @@ export async function fetchLanguages(): Promise { return result.map(omitVirtualClassFields); } -export async function fetchLangPack({ sourceLangPacks, langCode }: { sourceLangPacks: string[]; langCode: string }) { +export async function fetchLangPack({ sourceLangPacks, langCode }: { + sourceLangPacks: typeof LANG_PACKS; + langCode: string; +}) { const results = await Promise.all(sourceLangPacks.map((langPack) => { return invokeRequest(new GramJs.langpack.GetLangPack({ langPack, @@ -299,6 +302,22 @@ export async function fetchLangPack({ sourceLangPacks, langCode }: { sourceLangP return { langPack: Object.assign({}, ...collections.reverse()) }; } +export async function fetchLangStrings({ 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(omitVirtualClassFields); +} + export async function fetchPrivacySettings(privacyKey: ApiPrivacyKey) { const key = buildInputPrivacyKey(privacyKey); const result = await invokeRequest(new GramJs.account.GetPrivacy({ key })); diff --git a/src/components/auth/AuthCode.tsx b/src/components/auth/AuthCode.tsx index 069293204..8b0643a53 100644 --- a/src/components/auth/AuthCode.tsx +++ b/src/components/auth/AuthCode.tsx @@ -7,7 +7,9 @@ import { GlobalState, GlobalActions } from '../../global/types'; import { IS_TOUCH_ENV } from '../../util/environment'; import { pick } from '../../util/iteratees'; +import renderText from '../common/helpers/renderText'; import useHistoryBack from '../../hooks/useHistoryBack'; +import useLang from '../../hooks/useLang'; import InputText from '../ui/InputText'; import Loading from '../ui/Loading'; @@ -29,6 +31,7 @@ const AuthCode: FC = ({ returnToAuthPhoneNumber, clearAuthError, }) => { + const lang = useLang(); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); @@ -91,31 +94,21 @@ const AuthCode: FC = ({ onClick={returnToAuthPhoneNumber} role="button" tabIndex={0} - title="Sign In with another phone number" + title={lang('WrongNumber')} >

- {authIsCodeViaApp ? ( - <> - We have sent the code to the Telegram app -
on your other device. - - ) : ( - <> - We have sent you an SMS -
with the code. - - )} + {renderText(lang(authIsCodeViaApp ? 'SentAppCode' : 'Login.JustSentSms'), ['simple_markdown'])}

diff --git a/src/components/auth/AuthPassword.tsx b/src/components/auth/AuthPassword.tsx index e181872f0..d6e4b4ffd 100644 --- a/src/components/auth/AuthPassword.tsx +++ b/src/components/auth/AuthPassword.tsx @@ -6,6 +6,7 @@ import { withGlobal } from '../../lib/teact/teactn'; import { GlobalState, GlobalActions } from '../../global/types'; import { pick } from '../../util/iteratees'; +import useLang from '../../hooks/useLang'; import MonkeyPassword from '../common/PasswordMonkey'; import PasswordForm from '../common/PasswordForm'; @@ -16,6 +17,7 @@ type DispatchProps = Pick; const AuthPassword: FC = ({ authIsLoading, authError, authHint, setAuthPassword, clearAuthError, }) => { + const lang = useLang(); const [showPassword, setShowPassword] = useState(false); const handleChangePasswordVisibility = useCallback((isVisible) => { @@ -30,14 +32,11 @@ const AuthPassword: FC = ({
-

Enter Your Password

-

- Your account is protected with -
an additional password. -

+

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

+

{lang('Login.EnterPasswordDescription')}

; +)> & { + language?: LangCode; +}; type DispatchProps = Pick; const MIN_NUMBER_LENGTH = 7; @@ -43,19 +52,25 @@ const AuthPhoneNumber: FC = ({ authError, authRememberMe, authNearestCountry, + language, setAuthPhoneNumber, setAuthRememberMe, loadNearestCountry, clearAuthError, goToAuthQrCode, + setSettingOption, }) => { + const lang = useLang(); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); + const suggestedLanguage = getSuggestedLanguage(); + const continueText = useLangString(suggestedLanguage, 'ContinueOnThisLanguage'); const [country, setCountry] = useState(); const [phoneNumber, setPhoneNumber] = useState(); const [isTouched, setIsTouched] = useState(false); const [lastSelection, setLastSelection] = useState<[number, number] | undefined>(); + const [isLoading, markIsLoading, unmarkIsLoading] = useFlag(); const fullNumber = country ? `${country.code} ${phoneNumber || ''}` : phoneNumber; const canSubmit = fullNumber && fullNumber.replace(/[^\d]+/g, '').length >= MIN_NUMBER_LENGTH; @@ -99,6 +114,16 @@ const AuthPhoneNumber: FC = ({ setPhoneNumber(formatPhoneNumber(newFullNumber, selectedCountry)); }, [country]); + const handleLangChange = useCallback(() => { + markIsLoading(); + + setLanguage(suggestedLanguage!, () => { + unmarkIsLoading(); + + setSettingOption({ language: suggestedLanguage }); + }); + }, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]); + useEffect(() => { if (phoneNumber === undefined && authPhoneNumber) { parseFullNumber(authPhoneNumber); @@ -169,11 +194,8 @@ const AuthPhoneNumber: FC = ({
@@ -216,21 +241,31 @@ const AuthPhoneNumber: FC = ({ }; export default memo(withGlobal( - (global): StateProps => pick(global, [ - 'connectionState', - 'authState', - 'authPhoneNumber', - 'authIsLoading', - 'authIsLoadingQrCode', - 'authError', - 'authRememberMe', - 'authNearestCountry', - ]), + (global): StateProps => { + const { + settings: { byKey: { language } }, + } = global; + + return { + ...pick(global, [ + 'connectionState', + 'authState', + 'authPhoneNumber', + 'authIsLoading', + 'authIsLoadingQrCode', + 'authError', + 'authRememberMe', + 'authNearestCountry', + ]), + language, + }; + }, (setGlobal, actions): DispatchProps => pick(actions, [ 'setAuthPhoneNumber', 'setAuthRememberMe', 'clearAuthError', 'loadNearestCountry', 'goToAuthQrCode', + 'setSettingOption', ]), )(AuthPhoneNumber)); diff --git a/src/components/auth/AuthQrCode.tsx b/src/components/auth/AuthQrCode.tsx index e3de0c422..8ce6729ff 100644 --- a/src/components/auth/AuthQrCode.tsx +++ b/src/components/auth/AuthQrCode.tsx @@ -1,17 +1,29 @@ import QrCreator from 'qr-creator'; import React, { - FC, useEffect, useRef, memo, + FC, useEffect, useRef, memo, useCallback, } from '../../lib/teact/teact'; import { withGlobal } from '../../lib/teact/teactn'; + import { GlobalState, GlobalActions } from '../../global/types'; +import { LangCode } from '../../types'; import { pick } from '../../util/iteratees'; +import { setLanguage } from '../../util/langProvider'; +import renderText from '../common/helpers/renderText'; +import useLangString from '../../hooks/useLangString'; +import useFlag from '../../hooks/useFlag'; +import useLang from '../../hooks/useLang'; +import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; import Loading from '../ui/Loading'; import Button from '../ui/Button'; -type StateProps = Pick; -type DispatchProps = Pick; +type StateProps = Pick & { + language?: LangCode; +}; +type DispatchProps = Pick; const DATA_PREFIX = 'tg://login?token='; @@ -19,10 +31,16 @@ const AuthCode: FC = ({ connectionState, authState, authQrCode, + language, returnToAuthPhoneNumber, + setSettingOption, }) => { + const suggestedLanguage = getSuggestedLanguage(); + const lang = useLang(); // eslint-disable-next-line no-null/no-null const qrCodeRef = useRef(null); + const continueText = useLangString(suggestedLanguage, 'ContinueOnThisLanguage'); + const [isLoading, markIsLoading, unmarkIsLoading] = useFlag(); useEffect(() => { if (!authQrCode || connectionState !== 'connectionStateReady') { @@ -43,6 +61,16 @@ const AuthCode: FC = ({ }, container); }, [connectionState, authQrCode]); + const handleLangChange = useCallback(() => { + markIsLoading(); + + setLanguage(suggestedLanguage!, () => { + unmarkIsLoading(); + + setSettingOption({ language: suggestedLanguage }); + }); + }, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]); + const isAuthReady = authState === 'authorizationStateWaitQrCode'; return ( @@ -53,14 +81,17 @@ const AuthCode: FC = ({ ) : (
)} -

Log in to Telegram by QR Code

+

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

    -
  1. Open Telegram on your phone
  2. -
  3. Go to Settings > Devices > Scan QR
  4. -
  5. Point your phone at this screen to confirm login
  6. +
  7. {lang('Login.QR.Help1')}
  8. +
  9. {renderText(lang('Login.QR.Help2'), ['simple_markdown'])}
  10. +
  11. {lang('Login.QR.Help3')}
{isAuthReady && ( - + + )} + {suggestedLanguage && suggestedLanguage !== language && continueText && ( + )}
@@ -68,6 +99,19 @@ const AuthCode: FC = ({ }; export default memo(withGlobal( - (global): StateProps => pick(global, ['connectionState', 'authState', 'authQrCode']), - (setGlobal, actions): DispatchProps => pick(actions, ['returnToAuthPhoneNumber']), + (global): StateProps => { + const { + connectionState, authState, authQrCode, settings: { byKey: { language } }, + } = global; + + return { + connectionState, + authState, + authQrCode, + language, + }; + }, + (setGlobal, actions): DispatchProps => pick(actions, [ + 'returnToAuthPhoneNumber', 'setSettingOption', + ]), )(AuthCode)); diff --git a/src/components/auth/AuthRegister.tsx b/src/components/auth/AuthRegister.tsx index 39f888f19..3a39d802e 100644 --- a/src/components/auth/AuthRegister.tsx +++ b/src/components/auth/AuthRegister.tsx @@ -5,6 +5,7 @@ import { withGlobal } from '../../lib/teact/teactn'; import { GlobalState, GlobalActions } from '../../global/types'; import { pick } from '../../util/iteratees'; +import useLang from '../../hooks/useLang'; import Button from '../ui/Button'; import InputText from '../ui/InputText'; @@ -16,6 +17,7 @@ type DispatchProps = Pick = ({ authIsLoading, authError, signUp, clearAuthError, uploadProfilePhoto, }) => { + const lang = useLang(); const [isButtonShown, setIsButtonShown] = useState(false); const [croppedFile, setCroppedFile] = useState(); const [firstName, setFirstName] = useState(''); @@ -53,28 +55,25 @@ const AuthRegister: FC = ({
-

Your Name

-

- Enter your name and add -
a profile picture. -

+

{lang('YourName')}

+

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

{isButtonShown && ( - + )}
diff --git a/src/components/auth/CountryCodeInput.tsx b/src/components/auth/CountryCodeInput.tsx index 4586f5ca5..29a7f2743 100644 --- a/src/components/auth/CountryCodeInput.tsx +++ b/src/components/auth/CountryCodeInput.tsx @@ -2,17 +2,18 @@ import React, { FC, useState, memo, useCallback, useRef, } from '../../lib/teact/teact'; +import { ANIMATION_END_DELAY } from '../../config'; import { countryList } from '../../util/phoneNumber'; import searchWords from '../../util/searchWords'; import buildClassName from '../../util/buildClassName'; import renderText from '../common/helpers/renderText'; +import useLang from '../../hooks/useLang'; import DropdownMenu from '../ui/DropdownMenu'; import MenuItem from '../ui/MenuItem'; import Spinner from '../ui/Spinner'; import './CountryCodeInput.scss'; -import { ANIMATION_END_DELAY } from '../../config'; type OwnProps = { id: string; @@ -30,6 +31,7 @@ const CountryCodeInput: FC = ({ isLoading, onChange, }) => { + const lang = useLang(); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); @@ -103,7 +105,7 @@ const CountryCodeInput: FC = ({ onInput={handleInput} onKeyDown={handleInputKeyDown} /> - + {isLoading ? ( ) : ( @@ -136,7 +138,7 @@ const CountryCodeInput: FC = ({ className="no-results" disabled > - No countries matched your filter. + {lang('lng_country_none')} )} diff --git a/src/components/auth/helpers/getSuggestedLanguage.ts b/src/components/auth/helpers/getSuggestedLanguage.ts new file mode 100644 index 000000000..084413a0d --- /dev/null +++ b/src/components/auth/helpers/getSuggestedLanguage.ts @@ -0,0 +1,9 @@ +export function getSuggestedLanguage() { + let suggestedLanguage = navigator.language; + + if (suggestedLanguage && suggestedLanguage !== 'pt-br') { + suggestedLanguage = suggestedLanguage.substr(0, 2); + } + + return suggestedLanguage; +} diff --git a/src/components/left/settings/twoFa/SettingsTwoFa.tsx b/src/components/left/settings/twoFa/SettingsTwoFa.tsx index b872e89c1..7b5d8c232 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFa.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFa.tsx @@ -181,7 +181,7 @@ const SettingsTwoFa: FC = ({ return ( { + const [translation, setTranslation] = useState(); + + if (langCode) { + langProvider + .getTranslationForLangString(langCode, key) + .then(setTranslation); + } + + return translation; +}; diff --git a/src/lib/gramjs/tl/apiTl.js b/src/lib/gramjs/tl/apiTl.js index 2c917a156..65a7c2c1d 100644 --- a/src/lib/gramjs/tl/apiTl.js +++ b/src/lib/gramjs/tl/apiTl.js @@ -1084,6 +1084,7 @@ payments.validateRequestedInfo#db103170 flags:# save:flags.0?true peer:InputPeer payments.sendPaymentForm#30c3bc9d flags:# form_id:long peer:InputPeer msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; +langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; langpack.getLanguages#42c6978f lang_pack:string = Vector; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; // LAYER 128 diff --git a/src/lib/gramjs/tl/static/api.reduced.tl b/src/lib/gramjs/tl/static/api.reduced.tl index 2e30dd4ce..318c41f61 100644 --- a/src/lib/gramjs/tl/static/api.reduced.tl +++ b/src/lib/gramjs/tl/static/api.reduced.tl @@ -1084,6 +1084,7 @@ payments.validateRequestedInfo#db103170 flags:# save:flags.0?true peer:InputPeer payments.sendPaymentForm#30c3bc9d flags:# form_id:long peer:InputPeer msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; +langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; langpack.getLanguages#42c6978f lang_pack:string = Vector; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; // LAYER 128 diff --git a/src/modules/actions/api/initial.ts b/src/modules/actions/api/initial.ts index e4fc9c4bd..2082cb007 100644 --- a/src/modules/actions/api/initial.ts +++ b/src/modules/actions/api/initial.ts @@ -2,6 +2,7 @@ import { addReducer, getDispatch, getGlobal, setGlobal, } from '../../../lib/teact/teactn'; +import { initApi, callApi } from '../../../api/gramjs'; import { GlobalState } from '../../../global/types'; import { @@ -13,7 +14,6 @@ import { IS_TEST, } from '../../../config'; import { PLATFORM_ENV } from '../../../util/environment'; -import { initApi, callApi } from '../../../api/gramjs'; import { unsubscribe } from '../../../util/notifications'; import * as cacheApi from '../../../util/cacheApi'; import { updateAppBadge } from '../../../util/appBadge'; diff --git a/src/modules/actions/api/settings.ts b/src/modules/actions/api/settings.ts index 82fded80d..a0e2b61de 100644 --- a/src/modules/actions/api/settings.ts +++ b/src/modules/actions/api/settings.ts @@ -3,7 +3,7 @@ import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn'; import { GlobalState } from '../../../global/types'; import { ApiPrivacyKey, PrivacyVisibility, ProfileEditProgress, IInputPrivacyRules, IInputPrivacyContact, - UPLOADING_WALLPAPER_SLUG, + UPLOADING_WALLPAPER_SLUG, LangCode, } from '../../../types'; import { callApi } from '../../../api/gramjs'; diff --git a/src/modules/actions/initial.ts b/src/modules/actions/initial.ts index d4b2cb2ba..4194e327c 100644 --- a/src/modules/actions/initial.ts +++ b/src/modules/actions/initial.ts @@ -1,3 +1,5 @@ import './ui/initial'; +import './ui/settings'; import './api/initial'; +import './api/settings'; import './apiUpdaters/initial'; diff --git a/src/util/fallbackLangPack.ts b/src/util/fallbackLangPack.ts index f4626a381..2aec4732d 100644 --- a/src/util/fallbackLangPack.ts +++ b/src/util/fallbackLangPack.ts @@ -1705,4 +1705,104 @@ export default { key: 'lng_update_telegram', value: 'Update Telegram', }, + 'Login.QR.Title': { + key: 'Login.QR.Title', + value: 'Log in to Telegram by QR Code', + }, + PHONE_NUMBER_INVALID: { + key: 'PHONE_NUMBER_INVALID', + value: 'Invalid phone number', + }, + PHONE_CODE_INVALID: { + key: 'PHONE_CODE_INVALID', + value: 'Invalid code', + }, + PASSWORD_HASH_INVALID: { + key: 'PASSWORD_HASH_INVALID', + value: 'Incorrect password', + }, + WrongNumber: { + key: 'WrongNumber', + value: 'Wrong number?', + }, + SentAppCode: { + key: 'SentAppCode', + value: 'We\'ve sent the code to the **Telegram** app on your other device.', + }, + 'Login.JustSentSms': { + key: 'Login.JustSentSms', + value: 'We have sent you a code via SMS. Please enter it above.', + }, + 'Login.Header.Password': { + key: 'Login.Header.Password', + value: 'Enter Password', + }, + 'Login.EnterPasswordDescription': { + key: 'Login.EnterPasswordDescription', + value: 'You have Two-Step Verification enabled, so your account is protected with an additional password.', + }, + StartText: { + key: 'StartText', + value: 'Please confirm your country code and enter your phone number.', + }, + 'Login.PhonePlaceholder': { + key: 'Login.PhonePlaceholder', + value: 'Your phone number', + }, + 'Login.Next': { + key: 'Login.Next', + value: 'Next', + }, + 'Login.QR.Login': { + key: 'Login.QR.Login', + value: 'Log in by QR Code', + }, + ContinueOnThisLanguage: { + key: 'ContinueOnThisLanguage', + value: 'Continue in English', + }, + 'Login.QR.Help1': { + key: 'Login.QR.Help1', + value: 'Open Telegram on your phone', + }, + 'Login.QR.Help2': { + key: 'Login.QR.Help2', + value: 'Go to **Settings** > **Devices** > **Scan QR**', + }, + 'Login.QR.Help3': { + key: 'Login.QR.Help3', + value: 'Point your phone at this screen to confirm login', + }, + 'Login.QR.Cancel': { + key: 'Login.QR.Cancel', + value: 'Log in by phone Number', + }, + YourName: { + key: 'YourName', + value: 'Your Name', + }, + 'Login.Register.Desc': { + key: 'Login.Register.Desc', + value: 'Enter your name and add a profile picture.', + }, + 'Login.Register.FirstName.Placeholder': { + key: 'Login.Register.FirstName.Placeholder', + value: 'First Name', + }, + 'Login.Register.LastName.Placeholder': { + key: 'Login.Register.LastName.Placeholder', + value: 'Last Name', + }, + 'Login.SelectCountry.Title': { + key: 'Login.SelectCountry.Title', + value: 'Country', + }, + lng_country_none: { + key: 'lng_country_none', + value: 'Country not found', + }, + PleaseEnterPassword: { + key: 'PleaseEnterPassword', + value: 'Enter your new password', + }, } as ApiLangPack; diff --git a/src/util/langProvider.ts b/src/util/langProvider.ts index a812327ef..88d9ddb9c 100644 --- a/src/util/langProvider.ts +++ b/src/util/langProvider.ts @@ -1,6 +1,8 @@ -import { ApiLangPack } from '../api/types'; +import { ApiLangPack, ApiLangString } from '../api/types'; -import { LANG_CACHE_NAME, LANG_PACKS } from '../config'; +import { + DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, LANG_CACHE_NAME, LANG_PACKS, +} from '../config'; import * as cacheApi from './cacheApi'; import { callApi } from '../api/gramjs'; import { createCallbackManager } from './callbacks'; @@ -14,7 +16,6 @@ interface LangFn { code?: string; } -const FALLBACK_LANG_CODE = 'en'; const SUBSTITUTION_REGEX = /%\d?\$?[sdf@]/g; const PLURAL_OPTIONS = ['value', 'zeroValue', 'oneValue', 'twoValue', 'fewValue', 'manyValue', 'otherValue'] as const; const PLURAL_RULES = { @@ -77,24 +78,23 @@ export const getTranslation: LangFn = (key: string, value?: any, format?: 'i') = return key; } - const template = langString[typeof value === 'number' ? getPluralOption(value) : 'value']; - if (!template || !template.trim()) { - const parts = key.split('.'); - - return parts[parts.length - 1]; - } - - if (value !== undefined) { - const formattedValue = format === 'i' ? formatInteger(value) : value; - const result = processTemplate(template, formattedValue); - const cacheValue = Array.isArray(value) ? JSON.stringify(value) : value; - cache.set(`${key}_${cacheValue}_${format}`, result); - return result; - } - - return template; + return processTranslation(langString, key, value, format); }; +export async function getTranslationForLangString(langCode: string, key: string) { + let translateString = await cacheApi.fetch( + LANG_CACHE_NAME, + `${DEFAULT_LANG_PACK}_${langCode}_${key}`, + cacheApi.Type.Json, + ); + + if (!translateString) { + translateString = await fetchRemoteString(DEFAULT_LANG_PACK, langCode, key); + } + + return processTranslation(translateString, key); +} + export async function setLanguage(langCode: string, callback?: NoneToVoidFunction, withFallback = false) { if (langPack && langCode === currentLangCode) { if (callback) { @@ -153,8 +153,26 @@ async function fetchRemote(langCode: string): Promise { return undefined; } +async function fetchRemoteString( + remoteLangPack: typeof LANG_PACKS[number], langCode: string, key: string, +): Promise { + const remote = await callApi('fetchLangStrings', { + langPack: remoteLangPack, + langCode, + keys: [key], + }); + + if (remote && remote.length) { + await cacheApi.save(LANG_CACHE_NAME, `${remoteLangPack}_${langCode}_${key}`, remote[0]); + + return remote[0]; + } + + return undefined; +} + function getPluralOption(amount: number) { - const langCode = currentLangCode || FALLBACK_LANG_CODE; + const langCode = currentLangCode || DEFAULT_LANG_CODE; const optionIndex = PLURAL_RULES[langCode as keyof typeof PLURAL_RULES] ? PLURAL_RULES[langCode as keyof typeof PLURAL_RULES](amount) : 0; @@ -171,3 +189,22 @@ function processTemplate(template: string, value: any) { return `${result}${String(value[index] || '')}${str}`; }, initialValue || ''); } + +function processTranslation(langString: ApiLangString | undefined, key: string, value?: any, format?: 'i') { + const template = langString ? langString[typeof value === 'number' ? getPluralOption(value) : 'value'] : undefined; + if (!template || !template.trim()) { + const parts = key.split('.'); + + return parts[parts.length - 1]; + } + + if (value !== undefined) { + const formattedValue = format === 'i' ? formatInteger(value) : value; + const result = processTemplate(template, formattedValue); + const cacheValue = Array.isArray(value) ? JSON.stringify(value) : value; + cache.set(`${key}_${cacheValue}_${format}`, result); + return result; + } + + return template; +}