Localization: Better platform support (#5136)
This commit is contained in:
parent
6babbae9f9
commit
e678824a10
@ -3,38 +3,42 @@ import { readFileSync, writeFileSync } from 'fs';
|
|||||||
import readStrings from '../src/util/data/readStrings';
|
import readStrings from '../src/util/data/readStrings';
|
||||||
|
|
||||||
const TOP_COMMENT = '// This file is generated by dev/generateLangTypes.ts. Do not edit it manually.\n';
|
const TOP_COMMENT = '// This file is generated by dev/generateLangTypes.ts. Do not edit it manually.\n';
|
||||||
const LANG_KEY_TYPE = 'export type LangKey = keyof LangPair;';
|
const LANG_KEY_TYPE = `
|
||||||
|
export type RegularLangKey = keyof LangPair;
|
||||||
|
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||||
|
export type PluralLangKey = keyof LangPairPlural;
|
||||||
|
export type PluralLangKeyWithVariables = keyof LangPairPluralWithVariables;
|
||||||
|
export type LangKey = RegularLangKey | RegularLangKeyWithVariables | PluralLangKey | PluralLangKeyWithVariables;
|
||||||
|
type LangVariable = string | number | undefined;
|
||||||
|
`.trim();
|
||||||
|
|
||||||
const data = readFileSync('./src/assets/localization/fallback.strings', 'utf8');
|
const data = readFileSync('./src/assets/localization/fallback.strings', 'utf8');
|
||||||
|
|
||||||
const parsed = readStrings(data);
|
const parsed = readStrings(data);
|
||||||
const keysWithVars = Object.entries(parsed).reduce((acc, [keyWithSuffix, value]) => {
|
const regularKeysWithVars: Record<string, string[]> = {};
|
||||||
const key = keyWithSuffix.split('_')[0];
|
const pluralKeysWithVars: Record<string, string[]> = {};
|
||||||
|
Object.entries(parsed).forEach(([keyWithSuffix, value]) => {
|
||||||
|
const [key, pluralSuffix] = keyWithSuffix.split('_');
|
||||||
const variables = extractVariables(value);
|
const variables = extractVariables(value);
|
||||||
|
|
||||||
|
const acc = pluralSuffix ? pluralKeysWithVars : regularKeysWithVars;
|
||||||
|
|
||||||
const previousVariables = acc[key] || [];
|
const previousVariables = acc[key] || [];
|
||||||
if (!previousVariables.length) {
|
previousVariables.push(...variables);
|
||||||
acc[key] = variables;
|
acc[key] = previousVariables;
|
||||||
} else {
|
|
||||||
acc[key] = Array.from(new Set([...previousVariables, ...variables]));
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, string[]>);
|
|
||||||
|
|
||||||
let entries = '';
|
|
||||||
|
|
||||||
Object.entries(keysWithVars).forEach(([key, variables]) => {
|
|
||||||
const varString = variables.length
|
|
||||||
? `{\n ${variables.map((v) => `${wrapInQuotes(v)}: string | number;`).join('\n ')}\n };\n`
|
|
||||||
: 'undefined;\n';
|
|
||||||
entries += ` ${wrapInQuotes(key)}: ${varString}`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const langPair = `export interface LangPair {\n${entries}\n}\n`;
|
const regularTypes = formatKeyWithVariables(false, regularKeysWithVars);
|
||||||
const text = `${TOP_COMMENT}\n${langPair}\n${LANG_KEY_TYPE}\n`;
|
const pluralTypes = formatKeyWithVariables(true, pluralKeysWithVars);
|
||||||
|
|
||||||
|
const text = `${TOP_COMMENT}\n${regularTypes}\n${pluralTypes}${LANG_KEY_TYPE}\n`;
|
||||||
writeFileSync('./src/types/language.d.ts', text, 'utf8');
|
writeFileSync('./src/types/language.d.ts', text, 'utf8');
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Language types generated: ${Object.keys(keysWithVars).length} keys`);
|
console.log(`Language types generated
|
||||||
|
${Object.keys(regularKeysWithVars).length} simple keys
|
||||||
|
${Object.keys(pluralKeysWithVars).length} plural keys
|
||||||
|
`);
|
||||||
|
|
||||||
function extractVariables(value: string) {
|
function extractVariables(value: string) {
|
||||||
const matches = value.match(/(?<!\\){[^{}]+}/g);
|
const matches = value.match(/(?<!\\){[^{}]+}/g);
|
||||||
@ -45,3 +49,23 @@ function extractVariables(value: string) {
|
|||||||
function wrapInQuotes(value: string) {
|
function wrapInQuotes(value: string) {
|
||||||
return `'${value}'`;
|
return `'${value}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatKeyWithVariables(isPlural: boolean, keysWithVars: Record<string, string[]>) {
|
||||||
|
let entries = '';
|
||||||
|
let variableEntries = '';
|
||||||
|
|
||||||
|
Object.entries(keysWithVars).forEach(([key, variables]) => {
|
||||||
|
const uniqueVariables = variables.length ? Array.from(new Set(variables)) : undefined;
|
||||||
|
if (uniqueVariables) {
|
||||||
|
const type = `{\n ${uniqueVariables.map((v) => `${wrapInQuotes(v)}: V;`).join('\n ')}\n };\n`;
|
||||||
|
variableEntries += ` ${wrapInQuotes(key)}: ${type}`;
|
||||||
|
} else {
|
||||||
|
entries += ` ${wrapInQuotes(key)}: undefined;\n`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeName = isPlural ? 'LangPairPlural' : 'LangPair';
|
||||||
|
|
||||||
|
return `export interface ${typeName} {\n${entries}}\n
|
||||||
|
export interface ${typeName}WithVariables<V extends unknown = LangVariable> {\n${variableEntries}}\n`;
|
||||||
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import type {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
APP_CODE_NAME,
|
APP_CODE_NAME,
|
||||||
DEBUG, DEBUG_GRAMJS, IS_TEST, UPLOAD_WORKERS,
|
DEBUG, DEBUG_GRAMJS, IS_TEST, LANG_PACK, UPLOAD_WORKERS,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { pause } from '../../../util/schedulers';
|
import { pause } from '../../../util/schedulers';
|
||||||
import {
|
import {
|
||||||
@ -95,7 +95,9 @@ export async function init(initialArgs: ApiInitialArgs) {
|
|||||||
shouldForceHttpTransport,
|
shouldForceHttpTransport,
|
||||||
shouldAllowHttpTransport,
|
shouldAllowHttpTransport,
|
||||||
dcId,
|
dcId,
|
||||||
|
langPack: LANG_PACK,
|
||||||
langCode,
|
langCode,
|
||||||
|
systemLangCode: navigator.language,
|
||||||
isTestServerRequested,
|
isTestServerRequested,
|
||||||
} as any,
|
} as any,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import BigInt from 'big-integer';
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type { LANG_PACKS } from '../../../config';
|
import type { LANG_PACKS } from '../../../config';
|
||||||
import type { ApiInputPrivacyRules, ApiPrivacyKey, LangCode } from '../../../types';
|
import type { ApiInputPrivacyRules, ApiPrivacyKey } from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiAppConfig,
|
ApiAppConfig,
|
||||||
ApiConfig,
|
ApiConfig,
|
||||||
@ -14,7 +14,10 @@ import type {
|
|||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ACCEPTABLE_USERNAME_ERRORS, BLOCKED_LIST_LIMIT, DEFAULT_LANG_PACK, MAX_INT_32,
|
ACCEPTABLE_USERNAME_ERRORS,
|
||||||
|
BLOCKED_LIST_LIMIT,
|
||||||
|
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';
|
||||||
@ -466,7 +469,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: DEFAULT_LANG_PACK,
|
langPack: OLD_DEFAULT_LANG_PACK,
|
||||||
}));
|
}));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -646,7 +649,7 @@ export async function fetchTimezones(hash?: number) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCountryList({ langCode = 'en' }: { langCode?: LangCode }) {
|
export async function fetchCountryList({ langCode = 'en' }: { langCode?: string }) {
|
||||||
const countryList = await invokeRequest(new GramJs.help.GetCountriesList({
|
const countryList = await invokeRequest(new GramJs.help.GetCountriesList({
|
||||||
langCode,
|
langCode,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -47,6 +47,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
buildApiNotifyException,
|
buildApiNotifyException,
|
||||||
buildApiNotifyExceptionTopic,
|
buildApiNotifyExceptionTopic,
|
||||||
|
buildLangStrings,
|
||||||
buildPrivacyKey,
|
buildPrivacyKey,
|
||||||
} from '../apiBuilders/misc';
|
} from '../apiBuilders/misc';
|
||||||
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||||
@ -1050,7 +1051,20 @@ export function updater(update: Update) {
|
|||||||
'@type': 'updatePaidReactionPrivacy',
|
'@type': 'updatePaidReactionPrivacy',
|
||||||
isPrivate: update.private,
|
isPrivate: update.private,
|
||||||
});
|
});
|
||||||
} else if (update instanceof LocalUpdatePremiumFloodWait) {
|
} else if (update instanceof GramJs.UpdateLangPackTooLong) {
|
||||||
|
sendApiUpdate({
|
||||||
|
'@type': 'updateLangPackTooLong',
|
||||||
|
langCode: update.langCode,
|
||||||
|
});
|
||||||
|
} else if (update instanceof GramJs.UpdateLangPack) {
|
||||||
|
const { strings, keysToRemove } = buildLangStrings(update.difference.strings);
|
||||||
|
sendApiUpdate({
|
||||||
|
'@type': 'updateLangPack',
|
||||||
|
version: update.difference.version,
|
||||||
|
strings,
|
||||||
|
keysToRemove,
|
||||||
|
});
|
||||||
|
} else if (update instanceof LocalUpdatePremiumFloodWait) { // Local updates
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
'@type': 'updatePremiumFloodWait',
|
'@type': 'updatePremiumFloodWait',
|
||||||
isUpload: update.isUpload,
|
isUpload: update.isUpload,
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
import type { TeactNode } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiLimitType, ApiPremiumSection, CallbackAction } from '../../global/types';
|
import type { ApiLimitType, ApiPremiumSection, CallbackAction } from '../../global/types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
|
import type { LangFnParameters } from '../../util/localization';
|
||||||
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
||||||
import type { ApiUser } from './users';
|
import type { ApiUser } from './users';
|
||||||
|
|
||||||
@ -111,10 +114,11 @@ export type ApiNotifyException = {
|
|||||||
|
|
||||||
export type ApiNotification = {
|
export type ApiNotification = {
|
||||||
localId: string;
|
localId: string;
|
||||||
title?: string;
|
containerSelector?: string;
|
||||||
message: string;
|
title?: string | LangFnParameters;
|
||||||
|
message: TeactNode | LangFnParameters;
|
||||||
cacheBreaker?: string;
|
cacheBreaker?: string;
|
||||||
actionText?: string;
|
actionText?: string | LangFnParameters;
|
||||||
action?: CallbackAction | CallbackAction[];
|
action?: CallbackAction | CallbackAction[];
|
||||||
className?: string;
|
className?: string;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import type {
|
|||||||
import type {
|
import type {
|
||||||
ApiEmojiInteraction, ApiError, ApiNotifyException, ApiSessionData,
|
ApiEmojiInteraction, ApiError, ApiNotifyException, ApiSessionData,
|
||||||
} from './misc';
|
} from './misc';
|
||||||
|
import type { LangPackStringValue } from './settings';
|
||||||
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
|
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
|
||||||
import type {
|
import type {
|
||||||
ApiEmojiStatus, ApiUser, ApiUserFullInfo, ApiUserStatus,
|
ApiEmojiStatus, ApiUser, ApiUserFullInfo, ApiUserStatus,
|
||||||
@ -774,6 +775,18 @@ export type ApiUpdatePaidReactionPrivacy = {
|
|||||||
isPrivate: boolean;
|
isPrivate: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateLangPackTooLong = {
|
||||||
|
'@type': 'updateLangPackTooLong';
|
||||||
|
langCode: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateLangPack = {
|
||||||
|
'@type': 'updateLangPack';
|
||||||
|
version: number;
|
||||||
|
strings: Record<string, LangPackStringValue>;
|
||||||
|
keysToRemove: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdate = (
|
export type ApiUpdate = (
|
||||||
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
|
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
|
||||||
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
||||||
@ -806,7 +819,8 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
||||||
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance |
|
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance |
|
||||||
ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies | ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages |
|
ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies | ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages |
|
||||||
ApiUpdateDeleteProfilePhoto | ApiUpdateNewProfilePhoto | ApiUpdateEntities | ApiUpdatePaidReactionPrivacy
|
ApiUpdateDeleteProfilePhoto | ApiUpdateNewProfilePhoto | ApiUpdateEntities | ApiUpdatePaidReactionPrivacy |
|
||||||
|
ApiUpdateLangPackTooLong | ApiUpdateLangPack
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||||
|
|||||||
@ -668,7 +668,6 @@
|
|||||||
"DiscussChannel" = "channel";
|
"DiscussChannel" = "channel";
|
||||||
"ForwardedMessage" = "Forwarded message";
|
"ForwardedMessage" = "Forwarded message";
|
||||||
"ContextForwardMsg" = "Forward";
|
"ContextForwardMsg" = "Forward";
|
||||||
"ShareLinkCopied" = "Copied to Clipboard";
|
|
||||||
"MessageScheduleSend" = "Send Now";
|
"MessageScheduleSend" = "Send Now";
|
||||||
"MessageScheduleEditTime" = "Reschedule";
|
"MessageScheduleEditTime" = "Reschedule";
|
||||||
"Reply" = "Reply";
|
"Reply" = "Reply";
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { ApiCountryCode } from '../../api/types';
|
import type { ApiCountryCode } from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
import type { LangCode } from '../../types';
|
|
||||||
|
|
||||||
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||||
import { preloadImage } from '../../util/files';
|
import { preloadImage } from '../../util/files';
|
||||||
@ -36,7 +35,7 @@ type StateProps = Pick<GlobalState, (
|
|||||||
'authIsLoadingQrCode' | 'authError' |
|
'authIsLoadingQrCode' | 'authError' |
|
||||||
'authRememberMe' | 'authNearestCountry'
|
'authRememberMe' | 'authNearestCountry'
|
||||||
)> & {
|
)> & {
|
||||||
language?: LangCode;
|
language?: string;
|
||||||
phoneCodeList: ApiCountryCode[];
|
phoneCodeList: ApiCountryCode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
import type { LangCode } from '../../types';
|
|
||||||
|
|
||||||
import { DEFAULT_LANG_CODE, STRICTERDOM_ENABLED } from '../../config';
|
import { DEFAULT_LANG_CODE, STRICTERDOM_ENABLED } from '../../config';
|
||||||
import { disableStrict, enableStrict } from '../../lib/fasterdom/stricterdom';
|
import { disableStrict, enableStrict } from '../../lib/fasterdom/stricterdom';
|
||||||
@ -29,7 +28,7 @@ import blankUrl from '../../assets/blank.png';
|
|||||||
|
|
||||||
type StateProps =
|
type StateProps =
|
||||||
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
|
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
|
||||||
& { language?: LangCode };
|
& { language?: string };
|
||||||
|
|
||||||
const DATA_PREFIX = 'tg://login?token=';
|
const DATA_PREFIX = 'tg://login?token=';
|
||||||
const QR_SIZE = 280;
|
const QR_SIZE = 280;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getActions } from '../../global';
|
|||||||
|
|
||||||
import type { ApiAudio, ApiMessage, ApiVoice } from '../../api/types';
|
import type { ApiAudio, ApiMessage, ApiVoice } from '../../api/types';
|
||||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
import type { ISettings } from '../../types';
|
import type { ISettings } from '../../types';
|
||||||
import { ApiMediaFormat } from '../../api/types';
|
import { ApiMediaFormat } from '../../api/types';
|
||||||
import { AudioOrigin } from '../../types';
|
import { AudioOrigin } from '../../types';
|
||||||
@ -495,7 +495,7 @@ function getSeeklineSpikeAmounts(isMobile?: boolean, withAvatar?: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderAudio(
|
function renderAudio(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
audio: ApiAudio,
|
audio: ApiAudio,
|
||||||
duration: number,
|
duration: number,
|
||||||
isPlaying: boolean,
|
isPlaying: boolean,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, {
|
|||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useCallback, useEffect, useMemo, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import { MAX_INT_32 } from '../../config';
|
import { MAX_INT_32 } from '../../config';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -401,7 +401,7 @@ function formatDay(year: number, month: number, day: number) {
|
|||||||
return `${year}-${month + 1}-${day}`;
|
return `${year}-${month + 1}-${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSubmitLabel(lang: LangFn, date: Date) {
|
function formatSubmitLabel(lang: OldLangFn, date: Date) {
|
||||||
const day = formatDateToString(date, lang.code);
|
const day = formatDateToString(date, lang.code);
|
||||||
const today = formatDateToString(new Date(), lang.code);
|
const today = formatDateToString(new Date(), lang.code);
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,9 @@ const InviteLink: FC<OwnProps> = ({
|
|||||||
const copyLink = useLastCallback(() => {
|
const copyLink = useLastCallback(() => {
|
||||||
copyTextToClipboard(link);
|
copyTextToClipboard(link);
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('LinkCopied'),
|
message: {
|
||||||
|
key: 'LinkCopied',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type {
|
|||||||
ApiChat, ApiGroupCall, ApiMessage, ApiTopic, ApiUser,
|
ApiChat, ApiGroupCall, ApiMessage, ApiTopic, ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import type { LangFn } from '../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../hooks/useOldLang';
|
||||||
import type { TextPart } from '../../../types';
|
import type { TextPart } from '../../../types';
|
||||||
|
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||||
@ -36,7 +36,7 @@ const MAX_LENGTH = 32;
|
|||||||
const NBSP = '\u00A0';
|
const NBSP = '\u00A0';
|
||||||
|
|
||||||
export function renderActionMessageText(
|
export function renderActionMessageText(
|
||||||
oldLang: LangFn,
|
oldLang: OldLangFn,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
actionOriginUser?: ApiUser,
|
actionOriginUser?: ApiUser,
|
||||||
actionOriginChat?: ApiChat,
|
actionOriginChat?: ApiChat,
|
||||||
@ -269,7 +269,7 @@ function renderProductContent(message: ApiMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderMessageContent(
|
function renderMessageContent(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
options: RenderOptions = {},
|
options: RenderOptions = {},
|
||||||
observeIntersectionForLoading?: ObserveFn,
|
observeIntersectionForLoading?: ObserveFn,
|
||||||
@ -318,7 +318,7 @@ function renderUserContent(sender: ApiUser, noLinks?: boolean): string | TextPar
|
|||||||
return <UserLink className="action-link" sender={sender}>{sender && renderText(text!)}</UserLink>;
|
return <UserLink className="action-link" sender={sender}>{sender && renderText(text!)}</UserLink>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderChatContent(lang: LangFn, chat: ApiChat, noLinks?: boolean): string | TextPart | undefined {
|
function renderChatContent(lang: OldLangFn, chat: ApiChat, noLinks?: boolean): string | TextPart | undefined {
|
||||||
const text = trimText(getChatTitle(lang, chat), MAX_LENGTH);
|
const text = trimText(getChatTitle(lang, chat), MAX_LENGTH);
|
||||||
|
|
||||||
if (noLinks) {
|
if (noLinks) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { getGlobal } from '../../../global';
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage, ApiSponsoredMessage } from '../../../api/types';
|
import type { ApiMessage, ApiSponsoredMessage } from '../../../api/types';
|
||||||
import type { LangFn } from '../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../hooks/useOldLang';
|
||||||
import type { TextPart } from '../../../types';
|
import type { TextPart } from '../../../types';
|
||||||
import { ApiMessageEntityTypes } from '../../../api/types';
|
import { ApiMessageEntityTypes } from '../../../api/types';
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ export function renderMessageText({
|
|||||||
|
|
||||||
// TODO Use Message Summary component instead
|
// TODO Use Message Summary component instead
|
||||||
export function renderMessageSummary(
|
export function renderMessageSummary(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
noEmoji = false,
|
noEmoji = false,
|
||||||
highlight?: string,
|
highlight?: string,
|
||||||
|
|||||||
@ -62,6 +62,7 @@ type OwnProps = {
|
|||||||
noScrollRestore?: boolean;
|
noScrollRestore?: boolean;
|
||||||
isViewOnly?: boolean;
|
isViewOnly?: boolean;
|
||||||
withDefaultPadding?: boolean;
|
withDefaultPadding?: boolean;
|
||||||
|
forceRenderAllItems?: boolean;
|
||||||
onFilterChange?: (value: string) => void;
|
onFilterChange?: (value: string) => void;
|
||||||
onDisabledClick?: (value: string, isSelected: boolean) => void;
|
onDisabledClick?: (value: string, isSelected: boolean) => void;
|
||||||
onLoadMore?: () => void;
|
onLoadMore?: () => void;
|
||||||
@ -86,6 +87,7 @@ const ItemPicker = ({
|
|||||||
itemInputType,
|
itemInputType,
|
||||||
itemClassName,
|
itemClassName,
|
||||||
withDefaultPadding,
|
withDefaultPadding,
|
||||||
|
forceRenderAllItems,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onDisabledClick,
|
onDisabledClick,
|
||||||
onLoadMore,
|
onLoadMore,
|
||||||
@ -163,7 +165,7 @@ const ItemPicker = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [viewportValuesList, getMore] = useInfiniteScroll(
|
const [viewportValuesList, getMore] = useInfiniteScroll(
|
||||||
onLoadMore, sortedItemValuesList, Boolean(filterValue),
|
onLoadMore, sortedItemValuesList, Boolean(forceRenderAllItems || filterValue),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFilterChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFilterChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type {
|
|||||||
ApiChat, ApiMessage, ApiMessageOutgoingStatus,
|
ApiChat, ApiMessage, ApiMessageOutgoingStatus,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { LangFn } from '../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getMessageIsSpoiler,
|
getMessageIsSpoiler,
|
||||||
@ -111,7 +111,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
function renderSummary(
|
function renderSummary(
|
||||||
lang: LangFn, message: ApiMessage, blobUrl?: string, searchQuery?: string, isRoundVideo?: boolean,
|
lang: OldLangFn, message: ApiMessage, blobUrl?: string, searchQuery?: string, isRoundVideo?: boolean,
|
||||||
) {
|
) {
|
||||||
if (!blobUrl) {
|
if (!blobUrl) {
|
||||||
return renderMessageSummary(lang, message, undefined, searchQuery);
|
return renderMessageSummary(lang, message, undefined, searchQuery);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ApiChat, ApiMessage, ApiUser } from '../../../../api/types';
|
import type { ApiChat, ApiMessage, ApiUser } from '../../../../api/types';
|
||||||
import type { LangFn } from '../../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from '../../../../global/helpers';
|
} from '../../../../global/helpers';
|
||||||
|
|
||||||
export function getSenderName(
|
export function getSenderName(
|
||||||
lang: LangFn, message: ApiMessage, chatsById: Record<string, ApiChat>, usersById: Record<string, ApiUser>,
|
lang: OldLangFn, message: ApiMessage, chatsById: Record<string, ApiChat>, usersById: Record<string, ApiUser>,
|
||||||
) {
|
) {
|
||||||
const { senderId } = message;
|
const { senderId } = message;
|
||||||
if (!senderId) {
|
if (!senderId) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiLanguage } from '../../../api/types';
|
||||||
import type { ISettings, LangCode } from '../../../types';
|
import type { ISettings, LangCode } from '../../../types';
|
||||||
import { SettingsScreens } from '../../../types';
|
import { SettingsScreens } from '../../../types';
|
||||||
|
|
||||||
@ -29,7 +30,8 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
isCurrentUserPremium: boolean;
|
isCurrentUserPremium: boolean;
|
||||||
} & Pick<ISettings, 'languages' | 'language' | 'canTranslate' | 'canTranslateChats' | 'doNotTranslate'>;
|
languages?: ApiLanguage[];
|
||||||
|
} & Pick<ISettings, | 'language' | 'canTranslate' | 'canTranslateChats' | 'doNotTranslate'>;
|
||||||
|
|
||||||
const SettingsLanguage: FC<OwnProps & StateProps> = ({
|
const SettingsLanguage: FC<OwnProps & StateProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
@ -44,7 +46,6 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadLanguages,
|
loadLanguages,
|
||||||
loadAttachBots,
|
|
||||||
setSettingOption,
|
setSettingOption,
|
||||||
openPremiumModal,
|
openPremiumModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
@ -70,8 +71,6 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
|
|||||||
unmarkIsLoading();
|
unmarkIsLoading();
|
||||||
|
|
||||||
setSettingOption({ language: langCode as LangCode });
|
setSettingOption({ language: langCode as LangCode });
|
||||||
|
|
||||||
loadAttachBots(); // Should be refetched every language change
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -167,6 +166,7 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
|
|||||||
<ItemPicker
|
<ItemPicker
|
||||||
items={options}
|
items={options}
|
||||||
selectedValue={selectedLanguage}
|
selectedValue={selectedLanguage}
|
||||||
|
forceRenderAllItems
|
||||||
onSelectedValueChange={handleChange}
|
onSelectedValueChange={handleChange}
|
||||||
itemInputType="radio"
|
itemInputType="radio"
|
||||||
className="settings-picker"
|
className="settings-picker"
|
||||||
@ -182,8 +182,9 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const {
|
const {
|
||||||
language, languages, canTranslate, canTranslateChats, doNotTranslate,
|
language, canTranslate, canTranslateChats, doNotTranslate,
|
||||||
} = global.settings.byKey;
|
} = global.settings.byKey;
|
||||||
|
const languages = global.settings.languages;
|
||||||
|
|
||||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { getActions, getGlobal, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { ApiChatFolder, ApiMessage, ApiUser } from '../../api/types';
|
import type { ApiChatFolder, ApiMessage, ApiUser } from '../../api/types';
|
||||||
import type { ApiLimitTypeWithModal, TabState } from '../../global/types';
|
import type { ApiLimitTypeWithModal, TabState } from '../../global/types';
|
||||||
import type { LangCode } from '../../types';
|
|
||||||
import { ElectronEvent } from '../../types/electron';
|
import { ElectronEvent } from '../../types/electron';
|
||||||
|
|
||||||
import { BASE_EMOJI_KEYWORD_LANG, DEBUG, INACTIVE_MARKER } from '../../config';
|
import { BASE_EMOJI_KEYWORD_LANG, DEBUG, INACTIVE_MARKER } from '../../config';
|
||||||
@ -43,6 +42,7 @@ import useInterval from '../../hooks/schedulers/useInterval';
|
|||||||
import useTimeout from '../../hooks/schedulers/useTimeout';
|
import useTimeout from '../../hooks/schedulers/useTimeout';
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture';
|
import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
@ -115,7 +115,6 @@ type StateProps = {
|
|||||||
openedCustomEmojiSetIds?: string[];
|
openedCustomEmojiSetIds?: string[];
|
||||||
activeGroupCallId?: string;
|
activeGroupCallId?: string;
|
||||||
isServiceChatReady?: boolean;
|
isServiceChatReady?: boolean;
|
||||||
language?: LangCode;
|
|
||||||
wasTimeFormatSetManually?: boolean;
|
wasTimeFormatSetManually?: boolean;
|
||||||
isPhoneCallActive?: boolean;
|
isPhoneCallActive?: boolean;
|
||||||
addedSetIds?: string[];
|
addedSetIds?: string[];
|
||||||
@ -170,7 +169,6 @@ const Main = ({
|
|||||||
openedCustomEmojiSetIds,
|
openedCustomEmojiSetIds,
|
||||||
isServiceChatReady,
|
isServiceChatReady,
|
||||||
withInterfaceAnimations,
|
withInterfaceAnimations,
|
||||||
language,
|
|
||||||
wasTimeFormatSetManually,
|
wasTimeFormatSetManually,
|
||||||
addedSetIds,
|
addedSetIds,
|
||||||
addedCustomEmojiIds,
|
addedCustomEmojiIds,
|
||||||
@ -257,6 +255,8 @@ const Main = ({
|
|||||||
console.log('>>> RENDER MAIN');
|
console.log('>>> RENDER MAIN');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
// Preload Calls bundle to initialize sounds for iOS
|
// Preload Calls bundle to initialize sounds for iOS
|
||||||
useTimeout(() => {
|
useTimeout(() => {
|
||||||
void loadBundle(Bundles.Calls);
|
void loadBundle(Bundles.Calls);
|
||||||
@ -349,13 +349,15 @@ const Main = ({
|
|||||||
// Language-based API calls
|
// Language-based API calls
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMasterTab) {
|
if (isMasterTab) {
|
||||||
if (language !== BASE_EMOJI_KEYWORD_LANG) {
|
if (lang.code !== BASE_EMOJI_KEYWORD_LANG) {
|
||||||
loadEmojiKeywords({ language: language! });
|
loadEmojiKeywords({ language: lang.code });
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCountryList({ langCode: language });
|
loadCountryList({ langCode: lang.code });
|
||||||
|
|
||||||
|
loadAttachBots();
|
||||||
}
|
}
|
||||||
}, [language, isMasterTab]);
|
}, [lang, isMasterTab]);
|
||||||
|
|
||||||
// Re-fetch cached saved emoji for `localDb`
|
// Re-fetch cached saved emoji for `localDb`
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -596,7 +598,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
byKey: {
|
byKey: {
|
||||||
language, wasTimeFormatSetManually,
|
wasTimeFormatSetManually,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currentUserId,
|
currentUserId,
|
||||||
@ -660,7 +662,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isServiceChatReady: selectIsServiceChatReady(global),
|
isServiceChatReady: selectIsServiceChatReady(global),
|
||||||
activeGroupCallId: isMasterTab ? global.groupCalls.activeGroupCallId : undefined,
|
activeGroupCallId: isMasterTab ? global.groupCalls.activeGroupCallId : undefined,
|
||||||
withInterfaceAnimations: selectCanAnimateInterface(global),
|
withInterfaceAnimations: selectCanAnimateInterface(global),
|
||||||
language,
|
|
||||||
wasTimeFormatSetManually,
|
wasTimeFormatSetManually,
|
||||||
isPhoneCallActive: isMasterTab ? Boolean(global.phoneCall) : undefined,
|
isPhoneCallActive: isMasterTab ? Boolean(global.phoneCall) : undefined,
|
||||||
addedSetIds: global.stickers.added.setIds,
|
addedSetIds: global.stickers.added.setIds,
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiNotification } from '../../api/types';
|
import type { ApiNotification } from '../../api/types';
|
||||||
|
|
||||||
import { selectTabState } from '../../global/selectors';
|
import { selectTabState } from '../../global/selectors';
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
import renderText from '../common/helpers/renderText';
|
|
||||||
|
|
||||||
import Notification from '../ui/Notification';
|
import Notification from '../ui/Notification';
|
||||||
|
|
||||||
@ -15,8 +14,6 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Notifications: FC<StateProps> = ({ notifications }) => {
|
const Notifications: FC<StateProps> = ({ notifications }) => {
|
||||||
const { dismissNotification } = getActions();
|
|
||||||
|
|
||||||
if (!notifications.length) {
|
if (!notifications.length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -24,23 +21,7 @@ const Notifications: FC<StateProps> = ({ notifications }) => {
|
|||||||
return (
|
return (
|
||||||
<div id="Notifications">
|
<div id="Notifications">
|
||||||
{notifications.map((notification) => (
|
{notifications.map((notification) => (
|
||||||
<Notification
|
<Notification notification={notification} />
|
||||||
key={notification.localId}
|
|
||||||
title={notification.title
|
|
||||||
? renderText(notification.title, ['simple_markdown', 'emoji', 'br', 'links']) : undefined}
|
|
||||||
action={notification.action}
|
|
||||||
actionText={notification.actionText}
|
|
||||||
className={notification.className}
|
|
||||||
duration={notification.duration}
|
|
||||||
icon={notification.icon}
|
|
||||||
cacheBreaker={notification.cacheBreaker}
|
|
||||||
message={renderText(notification.message, ['simple_markdown', 'emoji', 'br', 'links'])}
|
|
||||||
shouldDisableClickDismiss={notification.disableClickDismiss}
|
|
||||||
dismissAction={notification.dismissAction}
|
|
||||||
shouldShowTimer={notification.shouldShowTimer}
|
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
|
||||||
onDismiss={() => dismissNotification({ localId: notification.localId })}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, { memo, useCallback, useEffect } from '../../../../lib/teact/teact
|
|||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiLimitTypeWithModal } from '../../../../global/types';
|
import type { ApiLimitTypeWithModal } from '../../../../global/types';
|
||||||
import type { LangFn } from '../../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
import type { IconName } from '../../../../types/icons';
|
import type { IconName } from '../../../../types/icons';
|
||||||
|
|
||||||
import { MAX_UPLOAD_FILEPART_SIZE } from '../../../../config';
|
import { MAX_UPLOAD_FILEPART_SIZE } from '../../../../config';
|
||||||
@ -71,7 +71,7 @@ const LIMIT_ICON: Record<ApiLimitTypeWithModal, IconName> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LIMIT_VALUE_FORMATTER: Partial<Record<ApiLimitTypeWithModal, (...args: any[]) => string>> = {
|
const LIMIT_VALUE_FORMATTER: Partial<Record<ApiLimitTypeWithModal, (...args: any[]) => string>> = {
|
||||||
uploadMaxFileparts: (lang: LangFn, value: number) => {
|
uploadMaxFileparts: (lang: OldLangFn, value: number) => {
|
||||||
// The real size is not exactly 4gb, so we need to round it
|
// The real size is not exactly 4gb, so we need to round it
|
||||||
if (value === 8000) return lang('FileSize.GB', '4');
|
if (value === 8000) return lang('FileSize.GB', '4');
|
||||||
if (value === 4000) return lang('FileSize.GB', '2');
|
if (value === 4000) return lang('FileSize.GB', '2');
|
||||||
@ -88,7 +88,7 @@ function getLimiterDescription({
|
|||||||
premiumValue,
|
premiumValue,
|
||||||
valueFormatter,
|
valueFormatter,
|
||||||
}: {
|
}: {
|
||||||
lang: LangFn;
|
lang: OldLangFn;
|
||||||
limitType?: ApiLimitTypeWithModal;
|
limitType?: ApiLimitTypeWithModal;
|
||||||
isPremium?: boolean;
|
isPremium?: boolean;
|
||||||
canBuyPremium?: boolean;
|
canBuyPremium?: boolean;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, { memo } from '../../lib/teact/teact';
|
|||||||
|
|
||||||
import type { ApiTopic } from '../../api/types';
|
import type { ApiTopic } from '../../api/types';
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import { REM } from '../common/helpers/mediaDimensions';
|
import { REM } from '../common/helpers/mediaDimensions';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
@ -53,7 +53,7 @@ const NoMessages: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderTopic(lang: LangFn, topic: ApiTopic) {
|
function renderTopic(lang: OldLangFn, topic: ApiTopic) {
|
||||||
return (
|
return (
|
||||||
<div className="NoMessages">
|
<div className="NoMessages">
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
@ -69,13 +69,13 @@ function renderTopic(lang: LangFn, topic: ApiTopic) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderScheduled(lang: LangFn) {
|
function renderScheduled(lang: OldLangFn) {
|
||||||
return (
|
return (
|
||||||
<div className="empty"><span>{lang('ScheduledMessages.EmptyPlaceholder')}</span></div>
|
<div className="empty"><span>{lang('ScheduledMessages.EmptyPlaceholder')}</span></div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSavedMessages(lang: LangFn) {
|
function renderSavedMessages(lang: OldLangFn) {
|
||||||
return (
|
return (
|
||||||
<div className="NoMessages">
|
<div className="NoMessages">
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
@ -92,7 +92,7 @@ function renderSavedMessages(lang: LangFn) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderGroup(lang: LangFn) {
|
function renderGroup(lang: OldLangFn) {
|
||||||
return (
|
return (
|
||||||
<div className="NoMessages">
|
<div className="NoMessages">
|
||||||
<div className="wrapper" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="wrapper" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
|||||||
@ -117,7 +117,7 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal containerId="#middle-column-portals">
|
<Portal containerSelector="#middle-column-portals">
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={className}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import { STARS_ICON_PLACEHOLDER } from '../../../../config';
|
|||||||
import { replaceWithTeact } from '../../../../util/replaceWithTeact';
|
import { replaceWithTeact } from '../../../../util/replaceWithTeact';
|
||||||
import renderText from '../../../common/helpers/renderText';
|
import renderText from '../../../common/helpers/renderText';
|
||||||
|
|
||||||
import { type LangFn } from '../../../../hooks/useOldLang';
|
import { type OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../../common/icons/Icon';
|
||||||
|
|
||||||
export default function renderKeyboardButtonText(lang: LangFn, button: ApiKeyboardButton): TeactNode {
|
export default function renderKeyboardButtonText(lang: OldLangFn, button: ApiKeyboardButton): TeactNode {
|
||||||
if (button.type === 'receipt') {
|
if (button.type === 'receipt') {
|
||||||
return lang('PaymentReceipt');
|
return lang('PaymentReceipt');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,10 +13,11 @@ import type {
|
|||||||
ApiMessage, ApiPeer, ApiPoll, ApiPollAnswer,
|
ApiMessage, ApiPeer, ApiPoll, ApiPollAnswer,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import type { LangFn } from '../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import { selectPeer } from '../../../global/selectors';
|
import { selectPeer } from '../../../global/selectors';
|
||||||
import { formatMediaDuration } from '../../../util/dates/dateFormat';
|
import { formatMediaDuration } from '../../../util/dates/dateFormat';
|
||||||
|
import { getMessageKey } from '../../../util/keys/messageKey';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
@ -26,7 +27,6 @@ import useOldLang from '../../../hooks/useOldLang';
|
|||||||
import AvatarList from '../../common/AvatarList';
|
import AvatarList from '../../common/AvatarList';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import CheckboxGroup from '../../ui/CheckboxGroup';
|
import CheckboxGroup from '../../ui/CheckboxGroup';
|
||||||
import Notification from '../../ui/Notification';
|
|
||||||
import RadioGroup from '../../ui/RadioGroup';
|
import RadioGroup from '../../ui/RadioGroup';
|
||||||
import PollOption from './PollOption';
|
import PollOption from './PollOption';
|
||||||
|
|
||||||
@ -54,13 +54,14 @@ const Poll: FC<OwnProps> = ({
|
|||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
onSendVote,
|
onSendVote,
|
||||||
}) => {
|
}) => {
|
||||||
const { loadMessage, openPollResults, requestConfetti } = getActions();
|
const {
|
||||||
|
loadMessage, openPollResults, requestConfetti, showNotification,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
const { id: messageId, chatId } = message;
|
const { id: messageId, chatId } = message;
|
||||||
const { summary, results } = poll;
|
const { summary, results } = poll;
|
||||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||||
const [chosenOptions, setChosenOptions] = useState<string[]>([]);
|
const [chosenOptions, setChosenOptions] = useState<string[]>([]);
|
||||||
const [isSolutionShown, setIsSolutionShown] = useState<boolean>(false);
|
|
||||||
const [wasSubmitted, setWasSubmitted] = useState<boolean>(false);
|
const [wasSubmitted, setWasSubmitted] = useState<boolean>(false);
|
||||||
const [closePeriod, setClosePeriod] = useState<number>(
|
const [closePeriod, setClosePeriod] = useState<number>(
|
||||||
!summary.closed && summary.closeDate && summary.closeDate > 0
|
!summary.closed && summary.closeDate && summary.closeDate > 0
|
||||||
@ -176,13 +177,13 @@ const Poll: FC<OwnProps> = ({
|
|||||||
openPollResults({ chatId, messageId });
|
openPollResults({ chatId, messageId });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSolutionShow = useLastCallback(() => {
|
const showSolution = useLastCallback(() => {
|
||||||
setIsSolutionShown(true);
|
showNotification({
|
||||||
});
|
localId: getMessageKey(message),
|
||||||
|
message: renderTextWithEntities({ text: poll.results.solution!, entities: poll.results.solutionEntities }),
|
||||||
const handleSolutionHide = useLastCallback(() => {
|
duration: SOLUTION_DURATION,
|
||||||
setIsSolutionShown(false);
|
containerSelector: SOLUTION_CONTAINER_ID,
|
||||||
setWasSubmitted(false);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show the solution to quiz if the answer was incorrect
|
// Show the solution to quiz if the answer was incorrect
|
||||||
@ -190,7 +191,7 @@ const Poll: FC<OwnProps> = ({
|
|||||||
if (wasSubmitted && hasVoted && summary.quiz && results.results && poll.results.solution) {
|
if (wasSubmitted && hasVoted && summary.quiz && results.results && poll.results.solution) {
|
||||||
const correctResult = results.results.find((r) => r.isChosen && r.isCorrect);
|
const correctResult = results.results.find((r) => r.isChosen && r.isCorrect);
|
||||||
if (!correctResult) {
|
if (!correctResult) {
|
||||||
setIsSolutionShown(true);
|
showSolution();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [hasVoted, wasSubmitted, results.results, summary.quiz, poll.results.solution]);
|
}, [hasVoted, wasSubmitted, results.results, summary.quiz, poll.results.solution]);
|
||||||
@ -224,22 +225,8 @@ const Poll: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSolution() {
|
|
||||||
return (
|
|
||||||
isSolutionShown && poll.results.solution && (
|
|
||||||
<Notification
|
|
||||||
message={renderTextWithEntities({ text: poll.results.solution, entities: poll.results.solutionEntities })}
|
|
||||||
duration={SOLUTION_DURATION}
|
|
||||||
onDismiss={handleSolutionHide}
|
|
||||||
containerId={SOLUTION_CONTAINER_ID}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Poll" dir={lang.isRtl ? 'auto' : 'ltr'}>
|
<div className="Poll" dir={lang.isRtl ? 'auto' : 'ltr'}>
|
||||||
{renderSolution()}
|
|
||||||
<div className="poll-question">
|
<div className="poll-question">
|
||||||
{renderTextWithEntities({
|
{renderTextWithEntities({
|
||||||
text: summary.question.text,
|
text: summary.question.text,
|
||||||
@ -274,8 +261,7 @@ const Poll: FC<OwnProps> = ({
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
color="translucent"
|
color="translucent"
|
||||||
className="poll-quiz-help"
|
className="poll-quiz-help"
|
||||||
disabled={isSolutionShown}
|
onClick={showSolution}
|
||||||
onClick={handleSolutionShow}
|
|
||||||
ariaLabel="Show Solution"
|
ariaLabel="Show Solution"
|
||||||
>
|
>
|
||||||
<i className="icon icon-lamp" />
|
<i className="icon icon-lamp" />
|
||||||
@ -353,7 +339,7 @@ function getPollTypeString(summary: ApiPoll['summary']) {
|
|||||||
return summary.isPublic ? 'PublicPoll' : 'AnonymousPoll';
|
return summary.isPublic ? 'PublicPoll' : 'AnonymousPoll';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReadableVotersCount(lang: LangFn, isQuiz: true | undefined, count?: number) {
|
function getReadableVotersCount(lang: OldLangFn, isQuiz: true | undefined, count?: number) {
|
||||||
if (!count) {
|
if (!count) {
|
||||||
return lang(isQuiz ? 'Chat.Quiz.TotalVotesEmpty' : 'Chat.Poll.TotalVotesResultEmpty');
|
return lang(isQuiz ? 'Chat.Quiz.TotalVotesEmpty' : 'Chat.Poll.TotalVotesResultEmpty');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { getActions } from '../../../../global';
|
|||||||
import type {
|
import type {
|
||||||
ApiMessage, ApiPeer, ApiStory, ApiTopic, ApiUser,
|
ApiMessage, ApiPeer, ApiStory, ApiTopic, ApiUser,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { LangFn } from '../../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
import type { IAlbum, ThreadId } from '../../../../types';
|
import type { IAlbum, ThreadId } from '../../../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../../api/types';
|
||||||
import { MediaViewerOrigin } from '../../../../types';
|
import { MediaViewerOrigin } from '../../../../types';
|
||||||
@ -33,7 +33,7 @@ export default function useInnerHandlers({
|
|||||||
isRepliesChat,
|
isRepliesChat,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
}: {
|
}: {
|
||||||
lang: LangFn;
|
lang: OldLangFn;
|
||||||
selectMessage: (e: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => void;
|
selectMessage: (e: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => void;
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
|||||||
@ -7,17 +7,15 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
import type { ApiMessage, ApiUser } from '../../../api/types';
|
import type { ApiMessage, ApiUser } from '../../../api/types';
|
||||||
import type { GiftOption } from './GiftModal';
|
import type { GiftOption } from './GiftModal';
|
||||||
|
|
||||||
import { STARS_CURRENCY_CODE, STARS_ICON_PLACEHOLDER } from '../../../config';
|
import { STARS_CURRENCY_CODE } from '../../../config';
|
||||||
import { getUserFullName } from '../../../global/helpers';
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
|
import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import { formatInteger } from '../../../util/textFormat';
|
|
||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import Icon from '../../common/icons/Icon';
|
|
||||||
import PremiumProgress from '../../common/PremiumProgress';
|
import PremiumProgress from '../../common/PremiumProgress';
|
||||||
import ActionMessage from '../../middle/ActionMessage';
|
import ActionMessage from '../../middle/ActionMessage';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -175,16 +173,9 @@ function GiftComposer({
|
|||||||
function renderFooter() {
|
function renderFooter() {
|
||||||
const userFullName = getUserFullName(user)!;
|
const userFullName = getUserFullName(user)!;
|
||||||
|
|
||||||
const amount = isStarGift ? (
|
const amount = isStarGift
|
||||||
lang('StarsAmount', {
|
? formatCurrency(gift.stars, STARS_CURRENCY_CODE, lang.code, { iconClassName: 'star-amount-icon' })
|
||||||
amount: formatInteger(gift.stars),
|
: formatCurrency(gift.amount, gift.currency);
|
||||||
}, {
|
|
||||||
withNodes: true,
|
|
||||||
specialReplacement: {
|
|
||||||
[STARS_ICON_PLACEHOLDER]: <Icon className="star-amount-icon" name="star" />,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
) : formatCurrency(gift.amount, gift.currency);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
@ -201,9 +192,9 @@ function GiftComposer({
|
|||||||
isPrimary
|
isPrimary
|
||||||
progress={gift.availabilityRemains / gift.availabilityTotal!}
|
progress={gift.availabilityRemains / gift.availabilityTotal!}
|
||||||
rightText={lang('GiftSoldCount', {
|
rightText={lang('GiftSoldCount', {
|
||||||
count: formatInteger(gift.availabilityTotal! - gift.availabilityRemains),
|
count: gift.availabilityTotal! - gift.availabilityRemains,
|
||||||
})}
|
})}
|
||||||
leftText={lang('GiftLeftCount', { count: formatInteger(gift.availabilityRemains) })}
|
leftText={lang('GiftLeftCount', { count: gift.availabilityRemains })}
|
||||||
className={styles.limited}
|
className={styles.limited}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -52,7 +52,9 @@ function GiftItemPremium({
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
function renderMonths() {
|
function renderMonths() {
|
||||||
const caption = months === 12 ? lang('Years', { count: 1 }) : lang('Months', { count: months });
|
const caption = months === 12
|
||||||
|
? lang('Years', { count: 1 }, { pluralValue: 1 })
|
||||||
|
: lang('Months', { count: months }, { pluralValue: months });
|
||||||
return (
|
return (
|
||||||
<div className={styles.monthsDescription}>
|
<div className={styles.monthsDescription}>
|
||||||
{caption}
|
{caption}
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import { getActions, withGlobal } from '../../../../global';
|
|||||||
import type { ApiSticker, ApiUser } from '../../../../api/types';
|
import type { ApiSticker, ApiUser } from '../../../../api/types';
|
||||||
import type { TabState } from '../../../../global/types';
|
import type { TabState } from '../../../../global/types';
|
||||||
|
|
||||||
import { STARS_ICON_PLACEHOLDER } from '../../../../config';
|
|
||||||
import { getUserFullName } from '../../../../global/helpers';
|
import { getUserFullName } from '../../../../global/helpers';
|
||||||
import { selectStarGiftSticker, selectUser } from '../../../../global/selectors';
|
import { selectStarGiftSticker, selectUser } from '../../../../global/selectors';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||||
|
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';
|
||||||
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
|
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
|
||||||
import { formatInteger } from '../../../../util/textFormat';
|
import { formatInteger } from '../../../../util/textFormat';
|
||||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||||
@ -69,6 +69,7 @@ const GiftInfoModal = ({
|
|||||||
const canConvertDifference = (userGift && starGiftMaxConvertPeriod && (
|
const canConvertDifference = (userGift && starGiftMaxConvertPeriod && (
|
||||||
userGift.date + starGiftMaxConvertPeriod - Date.now() / 1000
|
userGift.date + starGiftMaxConvertPeriod - Date.now() / 1000
|
||||||
)) || 0;
|
)) || 0;
|
||||||
|
const conversionLeft = Math.ceil(canConvertDifference / 60 / 60 / 24);
|
||||||
|
|
||||||
const handleClose = useLastCallback(() => {
|
const handleClose = useLastCallback(() => {
|
||||||
closeGiftInfoModal();
|
closeGiftInfoModal();
|
||||||
@ -131,17 +132,19 @@ const GiftInfoModal = ({
|
|||||||
|
|
||||||
return canUpdate
|
return canUpdate
|
||||||
? lang('GiftInfoDescription', {
|
? lang('GiftInfoDescription', {
|
||||||
amount: formatInteger(starsToConvert!),
|
amount: starsToConvert,
|
||||||
}, {
|
}, {
|
||||||
withNodes: true,
|
withNodes: true,
|
||||||
withMarkdown: true,
|
withMarkdown: true,
|
||||||
|
pluralValue: starsToConvert,
|
||||||
})
|
})
|
||||||
: lang('GiftInfoDescriptionOut', {
|
: lang('GiftInfoDescriptionOut', {
|
||||||
amount: formatInteger(starsToConvert!),
|
amount: starsToConvert,
|
||||||
user: getUserFullName(targetUser)!,
|
user: getUserFullName(targetUser)!,
|
||||||
}, {
|
}, {
|
||||||
withNodes: true,
|
withNodes: true,
|
||||||
withMarkdown: true,
|
withMarkdown: true,
|
||||||
|
pluralValue: starsToConvert,
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -205,14 +208,7 @@ const GiftInfoModal = ({
|
|||||||
tableData.push([
|
tableData.push([
|
||||||
lang('GiftInfoValue'),
|
lang('GiftInfoValue'),
|
||||||
<div className={styles.giftValue}>
|
<div className={styles.giftValue}>
|
||||||
{lang('StarsAmount', {
|
{formatStarsAsIcon(lang, gift.stars)}
|
||||||
amount: formatInteger(gift.stars),
|
|
||||||
}, {
|
|
||||||
withNodes: true,
|
|
||||||
specialReplacement: {
|
|
||||||
[STARS_ICON_PLACEHOLDER]: <StarIcon type="gold" size="small" />,
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
{canUpdate && canConvertDifference > 0 && Boolean(starsToConvert) && (
|
{canUpdate && canConvertDifference > 0 && Boolean(starsToConvert) && (
|
||||||
<BadgeButton onClick={openConvertConfirm}>
|
<BadgeButton onClick={openConvertConfirm}>
|
||||||
{lang('GiftInfoConvert', { amount: starsToConvert }, { pluralValue: starsToConvert })}
|
{lang('GiftInfoConvert', { amount: starsToConvert }, { pluralValue: starsToConvert })}
|
||||||
@ -225,8 +221,10 @@ const GiftInfoModal = ({
|
|||||||
tableData.push([
|
tableData.push([
|
||||||
lang('GiftInfoAvailability'),
|
lang('GiftInfoAvailability'),
|
||||||
lang('GiftInfoAvailabilityValue', {
|
lang('GiftInfoAvailabilityValue', {
|
||||||
count: formatInteger(gift.availabilityRemains!),
|
count: gift.availabilityRemains || 0,
|
||||||
total: formatInteger(gift.availabilityTotal),
|
total: gift.availabilityTotal,
|
||||||
|
}, {
|
||||||
|
pluralValue: gift.availabilityRemains || 0,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -295,7 +293,7 @@ const GiftInfoModal = ({
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{lang('GiftInfoConvertDescription1', {
|
{lang('GiftInfoConvertDescription1', {
|
||||||
amount: lang('StarsAmountText', { amount: formatInteger(userGift.starsToConvert!) }),
|
amount: formatStarsAsText(lang, userGift.starsToConvert!),
|
||||||
user: getUserFullName(userFrom)!,
|
user: getUserFullName(userFrom)!,
|
||||||
}, {
|
}, {
|
||||||
withNodes: true,
|
withNodes: true,
|
||||||
@ -305,10 +303,11 @@ const GiftInfoModal = ({
|
|||||||
{canConvertDifference > 0 && (
|
{canConvertDifference > 0 && (
|
||||||
<div>
|
<div>
|
||||||
{lang('GiftInfoConvertDescriptionPeriod', {
|
{lang('GiftInfoConvertDescriptionPeriod', {
|
||||||
count: formatInteger(Math.ceil(canConvertDifference / 60 / 60 / 24)),
|
count: conversionLeft,
|
||||||
}, {
|
}, {
|
||||||
withNodes: true,
|
withNodes: true,
|
||||||
withMarkdown: true,
|
withMarkdown: true,
|
||||||
|
pluralValue: conversionLeft,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -94,11 +94,11 @@ const StarPaymentModal = ({
|
|||||||
if (subscriptionInfo) {
|
if (subscriptionInfo) {
|
||||||
return lang('StarsSubscribeText', {
|
return lang('StarsSubscribeText', {
|
||||||
chat: subscriptionInfo.title,
|
chat: subscriptionInfo.title,
|
||||||
amount: amount!,
|
amount,
|
||||||
}, {
|
}, {
|
||||||
withNodes: true,
|
withNodes: true,
|
||||||
withMarkdown: true,
|
withMarkdown: true,
|
||||||
pluralValue: amount,
|
pluralValue: amount!,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { ApiStarsTransaction } from '../../../../api/types';
|
import type { ApiStarsTransaction } from '../../../../api/types';
|
||||||
import type { LangFn } from '../../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import { buildStarsTransactionCustomPeer } from '../../../../global/helpers/payments';
|
import { buildStarsTransactionCustomPeer } from '../../../../global/helpers/payments';
|
||||||
|
|
||||||
export function getTransactionTitle(lang: LangFn, transaction: ApiStarsTransaction) {
|
export function getTransactionTitle(lang: OldLangFn, transaction: ApiStarsTransaction) {
|
||||||
if (transaction.extendedMedia) return lang('StarMediaPurchase');
|
if (transaction.extendedMedia) return lang('StarMediaPurchase');
|
||||||
if (transaction.subscriptionPeriod) return lang('StarSubscriptionPurchase');
|
if (transaction.subscriptionPeriod) return lang('StarSubscriptionPurchase');
|
||||||
if (transaction.isReaction) return lang('StarsReactionsSent');
|
if (transaction.isReaction) return lang('StarsReactionsSent');
|
||||||
|
|||||||
@ -73,6 +73,9 @@ const MinimizedWebAppModal = ({
|
|||||||
{
|
{
|
||||||
botName: activeTabName,
|
botName: activeTabName,
|
||||||
count: openedTabsCount - 1,
|
count: openedTabsCount - 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pluralValue: openedTabsCount - 1,
|
||||||
})}`
|
})}`
|
||||||
: activeTabName;
|
: activeTabName;
|
||||||
|
|
||||||
|
|||||||
@ -182,10 +182,6 @@ const useWebAppFrame = (
|
|||||||
data: null,
|
data: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// showNotification({
|
|
||||||
// message: 'Clipboard access is not supported in this client yet',
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'web_app_open_scan_qr_popup') {
|
if (eventType === 'web_app_open_scan_qr_popup') {
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import type {
|
|||||||
ApiWebDocument,
|
ApiWebDocument,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { FormEditDispatch } from '../../hooks/reducers/usePaymentReducer';
|
import type { FormEditDispatch } from '../../hooks/reducers/usePaymentReducer';
|
||||||
import type { LangCode } from '../../types';
|
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import { PaymentStep } from '../../types';
|
import { PaymentStep } from '../../types';
|
||||||
|
|
||||||
@ -238,7 +237,7 @@ const Checkout: FC<OwnProps> = ({
|
|||||||
export default memo(Checkout);
|
export default memo(Checkout);
|
||||||
|
|
||||||
function renderPaymentItem(
|
function renderPaymentItem(
|
||||||
langCode: LangCode | undefined, title: string, value: number, currency: string, main = false,
|
langCode: string | undefined, title: string, value: number, currency: string, main = false,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.priceInfoItem, main && styles.priceInfoItemMain)}>
|
<div className={buildClassName(styles.priceInfoItem, main && styles.priceInfoItemMain)}>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, { memo, useCallback } from '../../../lib/teact/teact';
|
|||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage, StatisticsMessageInteractionCounter } from '../../../api/types';
|
import type { ApiMessage, StatisticsMessageInteractionCounter } from '../../../api/types';
|
||||||
import type { LangFn } from '../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getMessageMediaHash,
|
getMessageMediaHash,
|
||||||
@ -67,7 +67,7 @@ const StatisticsRecentMessage: FC<OwnProps> = ({ postStatistic, message }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderSummary(lang: LangFn, message: ApiMessage, blobUrl?: string, isRoundVideo?: boolean) {
|
function renderSummary(lang: OldLangFn, message: ApiMessage, blobUrl?: string, isRoundVideo?: boolean) {
|
||||||
if (!blobUrl) {
|
if (!blobUrl) {
|
||||||
return renderMessageSummary(lang, message);
|
return renderMessageSummary(lang, message);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type {
|
|||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
StatisticsStoryInteractionCounter,
|
StatisticsStoryInteractionCounter,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { LangFn } from '../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import { getStoryMediaHash } from '../../../global/helpers';
|
import { getStoryMediaHash } from '../../../global/helpers';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
@ -65,7 +65,7 @@ function StatisticsRecentStory({ chat, story, postStatistic }: OwnProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSummary(lang: LangFn, chat: ApiChat, blobUrl?: string) {
|
function renderSummary(lang: OldLangFn, chat: ApiChat, blobUrl?: string) {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{blobUrl ? (
|
{blobUrl ? (
|
||||||
|
|||||||
@ -9,6 +9,9 @@ const storedParameter: LangFnParameters = {
|
|||||||
variables: {
|
variables: {
|
||||||
count: 42,
|
count: 42,
|
||||||
},
|
},
|
||||||
|
options: {
|
||||||
|
pluralValue: 42,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const storedAdvancedParameter: LangFnParameters = {
|
const storedAdvancedParameter: LangFnParameters = {
|
||||||
@ -39,7 +42,7 @@ const TestLocale = () => {
|
|||||||
withMarkdown: true,
|
withMarkdown: true,
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
<p>{lang('Participants', { count: 42 })}</p>
|
<p>{lang('Participants', { count: 42 }, { pluralValue: 42 })}</p>
|
||||||
<p>
|
<p>
|
||||||
{lang('ChatServiceGroupUpdatedPinnedMessage1', {
|
{lang('ChatServiceGroupUpdatedPinnedMessage1', {
|
||||||
message: 'Some message',
|
message: 'Some message',
|
||||||
|
|||||||
@ -1,19 +1,21 @@
|
|||||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { CallbackAction } from '../../global/types';
|
import type { ApiNotification } from '../../api/types';
|
||||||
import type { IconName } from '../../types/icons';
|
import { isLangFnParam } from '../../util/localization/types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY } from '../../config';
|
import { ANIMATION_END_DELAY } from '../../config';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
||||||
|
|
||||||
@ -25,57 +27,55 @@ import RoundTimer from './RoundTimer';
|
|||||||
import './Notification.scss';
|
import './Notification.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
title?: TeactNode;
|
notification: ApiNotification;
|
||||||
containerId?: string;
|
|
||||||
message: TeactNode;
|
|
||||||
duration?: number;
|
|
||||||
action?: CallbackAction | CallbackAction[];
|
|
||||||
actionText?: string;
|
|
||||||
className?: string;
|
|
||||||
icon?: IconName;
|
|
||||||
shouldDisableClickDismiss?: boolean;
|
|
||||||
dismissAction?: CallbackAction;
|
|
||||||
shouldShowTimer?: boolean;
|
|
||||||
cacheBreaker?: string;
|
|
||||||
onDismiss: NoneToVoidFunction;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_DURATION = 3000;
|
const DEFAULT_DURATION = 3000;
|
||||||
const ANIMATION_DURATION = 150;
|
const ANIMATION_DURATION = 150;
|
||||||
|
|
||||||
const Notification: FC<OwnProps> = ({
|
const Notification: FC<OwnProps> = ({
|
||||||
title,
|
notification,
|
||||||
className,
|
|
||||||
message,
|
|
||||||
duration = DEFAULT_DURATION,
|
|
||||||
containerId,
|
|
||||||
icon,
|
|
||||||
action,
|
|
||||||
actionText,
|
|
||||||
shouldDisableClickDismiss,
|
|
||||||
dismissAction,
|
|
||||||
shouldShowTimer,
|
|
||||||
cacheBreaker,
|
|
||||||
onDismiss,
|
|
||||||
}) => {
|
}) => {
|
||||||
const actions = getActions();
|
const actions = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const {
|
||||||
|
localId,
|
||||||
|
message,
|
||||||
|
action,
|
||||||
|
actionText,
|
||||||
|
cacheBreaker,
|
||||||
|
className,
|
||||||
|
disableClickDismiss,
|
||||||
|
dismissAction,
|
||||||
|
duration = DEFAULT_DURATION,
|
||||||
|
icon,
|
||||||
|
shouldShowTimer,
|
||||||
|
title,
|
||||||
|
containerSelector,
|
||||||
|
} = notification;
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const timerRef = useRef<number | undefined>(null);
|
const timerRef = useRef<number | undefined>(null);
|
||||||
const { transitionClassNames } = useShowTransitionDeprecated(isOpen);
|
const { transitionClassNames } = useShowTransitionDeprecated(isOpen);
|
||||||
|
|
||||||
|
const handleDismiss = useLastCallback(() => {
|
||||||
|
actions.dismissNotification({ localId });
|
||||||
|
});
|
||||||
|
|
||||||
const closeAndDismiss = useLastCallback((force?: boolean) => {
|
const closeAndDismiss = useLastCallback((force?: boolean) => {
|
||||||
if (!force && shouldDisableClickDismiss) return;
|
if (!force && disableClickDismiss) return;
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setTimeout(onDismiss, ANIMATION_DURATION + ANIMATION_END_DELAY);
|
setTimeout(handleDismiss, ANIMATION_DURATION + ANIMATION_END_DELAY);
|
||||||
if (dismissAction) {
|
if (dismissAction) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
actions[dismissAction.action](dismissAction.payload);
|
actions[dismissAction.action](dismissAction.payload);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (action) {
|
if (action) {
|
||||||
if (Array.isArray(action)) {
|
if (Array.isArray(action)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -86,7 +86,7 @@ const Notification: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
closeAndDismiss();
|
closeAndDismiss();
|
||||||
}, [action, actions, closeAndDismiss]);
|
});
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(closeAndDismiss) : undefined), [isOpen, closeAndDismiss]);
|
useEffect(() => (isOpen ? captureEscKeyListener(closeAndDismiss) : undefined), [isOpen, closeAndDismiss]);
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ const Notification: FC<OwnProps> = ({
|
|||||||
}, [duration, cacheBreaker]); // Reset timer if `cacheBreaker` changes
|
}, [duration, cacheBreaker]); // Reset timer if `cacheBreaker` changes
|
||||||
|
|
||||||
const handleMouseEnter = useLastCallback(() => {
|
const handleMouseEnter = useLastCallback(() => {
|
||||||
if (shouldDisableClickDismiss) return;
|
if (disableClickDismiss) return;
|
||||||
if (timerRef.current) {
|
if (timerRef.current) {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
timerRef.current = undefined;
|
timerRef.current = undefined;
|
||||||
@ -110,15 +110,45 @@ const Notification: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleMouseLeave = useLastCallback(() => {
|
const handleMouseLeave = useLastCallback(() => {
|
||||||
if (shouldDisableClickDismiss) return;
|
if (disableClickDismiss) return;
|
||||||
if (timerRef.current) {
|
if (timerRef.current) {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
}
|
}
|
||||||
timerRef.current = window.setTimeout(closeAndDismiss, duration);
|
timerRef.current = window.setTimeout(closeAndDismiss, duration);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const renderedTitle = useMemo(() => {
|
||||||
|
if (!title) return undefined;
|
||||||
|
if (isLangFnParam(title)) {
|
||||||
|
return lang.with(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderText(title, ['simple_markdown', 'emoji', 'br', 'links']);
|
||||||
|
}, [lang, title]);
|
||||||
|
|
||||||
|
const renderedMessage = useMemo(() => {
|
||||||
|
if (isLangFnParam(message)) {
|
||||||
|
return lang.with(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof message === 'string') {
|
||||||
|
return renderText(message, ['simple_markdown', 'emoji', 'br', 'links']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}, [lang, message]);
|
||||||
|
|
||||||
|
const renderedActionText = useMemo(() => {
|
||||||
|
if (!actionText) return undefined;
|
||||||
|
if (isLangFnParam(actionText)) {
|
||||||
|
return lang.with(actionText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionText;
|
||||||
|
}, [lang, actionText]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal className="Notification-container" containerId={containerId}>
|
<Portal className="Notification-container" containerSelector={containerSelector}>
|
||||||
<div
|
<div
|
||||||
className={buildClassName('Notification', transitionClassNames, className)}
|
className={buildClassName('Notification', transitionClassNames, className)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
@ -127,16 +157,18 @@ const Notification: FC<OwnProps> = ({
|
|||||||
>
|
>
|
||||||
<Icon name={icon || 'info-filled'} className="notification-icon" />
|
<Icon name={icon || 'info-filled'} className="notification-icon" />
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{title && <div className="notification-title">{title}</div>}
|
{renderedTitle && (
|
||||||
{message}
|
<div className="notification-title">{renderedTitle}</div>
|
||||||
|
)}
|
||||||
|
{renderedMessage}
|
||||||
</div>
|
</div>
|
||||||
{action && actionText && (
|
{action && renderedActionText && (
|
||||||
<Button
|
<Button
|
||||||
color="translucent-white"
|
color="translucent-white"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className="notification-button"
|
className="notification-button"
|
||||||
>
|
>
|
||||||
{actionText}
|
{renderedActionText}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{shouldShowTimer && (
|
{shouldShowTimer && (
|
||||||
|
|||||||
@ -3,19 +3,19 @@ import { useLayoutEffect, useRef } from '../../lib/teact/teact';
|
|||||||
import TeactDOM from '../../lib/teact/teact-dom';
|
import TeactDOM from '../../lib/teact/teact-dom';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
containerId?: string;
|
containerSelector?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
children: VirtualElement;
|
children: VirtualElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Portal: FC<OwnProps> = ({ containerId, className, children }) => {
|
const Portal: FC<OwnProps> = ({ containerSelector, className, children }) => {
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
if (!elementRef.current) {
|
if (!elementRef.current) {
|
||||||
elementRef.current = document.createElement('div');
|
elementRef.current = document.createElement('div');
|
||||||
}
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const container = document.querySelector<HTMLDivElement>(containerId || '#portals');
|
const container = document.querySelector<HTMLDivElement>(containerSelector || '#portals');
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ const Portal: FC<OwnProps> = ({ containerId, className, children }) => {
|
|||||||
TeactDOM.render(undefined, element);
|
TeactDOM.render(undefined, element);
|
||||||
container.removeChild(element);
|
container.removeChild(element);
|
||||||
};
|
};
|
||||||
}, [className, containerId]);
|
}, [className, containerSelector]);
|
||||||
|
|
||||||
return TeactDOM.render(children, elementRef.current);
|
return TeactDOM.render(children, elementRef.current);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -294,6 +294,7 @@ export const PURCHASE_USERNAME = 'auction';
|
|||||||
export const ACCEPTABLE_USERNAME_ERRORS = new Set([USERNAME_PURCHASE_ERROR, 'USERNAME_INVALID']);
|
export const ACCEPTABLE_USERNAME_ERRORS = new Set([USERNAME_PURCHASE_ERROR, 'USERNAME_INVALID']);
|
||||||
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
||||||
export const WEB_APP_PLATFORM = 'weba';
|
export const WEB_APP_PLATFORM = 'weba';
|
||||||
|
export const LANG_PACK = 'weba';
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export const COUNTRIES_WITH_12H_TIME_FORMAT = new Set(['AU', 'BD', 'CA', 'CO', 'EG', 'HN', 'IE', 'IN', 'JO', 'MX', 'MY', 'NI', 'NZ', 'PH', 'PK', 'SA', 'SV', 'US']);
|
export const COUNTRIES_WITH_12H_TIME_FORMAT = new Set(['AU', 'BD', 'CA', 'CO', 'EG', 'HN', 'IE', 'IN', 'JO', 'MX', 'MY', 'NI', 'NZ', 'PH', 'PK', 'SA', 'SV', 'US']);
|
||||||
@ -321,7 +322,7 @@ 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 DEFAULT_LANG_PACK = 'android';
|
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';
|
||||||
|
|||||||
@ -373,7 +373,13 @@ addActionHandler('loadLanguages', async (global): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = replaceSettings(global, { languages: result });
|
global = {
|
||||||
|
...global,
|
||||||
|
settings: {
|
||||||
|
...global.settings,
|
||||||
|
languages: result,
|
||||||
|
},
|
||||||
|
};
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type {
|
|||||||
ApiUpdateServerTimeOffset,
|
ApiUpdateServerTimeOffset,
|
||||||
ApiUpdateSession,
|
ApiUpdateSession,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
import type { LangCode } from '../../../types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type { ActionReturnType, GlobalState } from '../../types';
|
import type { ActionReturnType, GlobalState } from '../../types';
|
||||||
|
|
||||||
@ -97,7 +98,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function onUpdateApiReady<T extends GlobalState>(global: T) {
|
function onUpdateApiReady<T extends GlobalState>(global: T) {
|
||||||
void oldSetLanguage(global.settings.byKey.language);
|
void oldSetLanguage(global.settings.byKey.language as LangCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpdateAuthorizationState<T extends GlobalState>(global: T, update: ApiUpdateAuthorizationState) {
|
function onUpdateAuthorizationState<T extends GlobalState>(global: T, update: ApiUpdateAuthorizationState) {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
import { PaymentStep } from '../../../types';
|
import { PaymentStep } from '../../../types';
|
||||||
|
|
||||||
|
import { applyLangPackDifference, requestLangPackDifference } from '../../../util/localization';
|
||||||
import { addActionHandler, setGlobal } from '../../index';
|
import { addActionHandler, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
addBlockedUser,
|
addBlockedUser,
|
||||||
@ -194,6 +195,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'updateLangPackTooLong': {
|
||||||
|
requestLangPackDifference(update.langCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'updateLangPack': {
|
||||||
|
applyLangPackDifference(update.version, update.strings, update.keysToRemove);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -199,7 +199,9 @@ addActionHandler('createGroupCallInviteLink', async (global, actions, payload):
|
|||||||
|
|
||||||
copyTextToClipboard(inviteLink);
|
copyTextToClipboard(inviteLink);
|
||||||
actions.showNotification({
|
actions.showNotification({
|
||||||
message: 'Link copied to clipboard',
|
message: {
|
||||||
|
key: 'LinkCopied',
|
||||||
|
},
|
||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { addCallback } from '../../../lib/teact/teactn';
|
import { addCallback } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
|
import type { LangCode } from '../../../types';
|
||||||
import type { ActionReturnType, GlobalState } from '../../types';
|
import type { ActionReturnType, GlobalState } from '../../types';
|
||||||
|
|
||||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
@ -134,7 +135,7 @@ addCallback((global: GlobalState) => {
|
|||||||
|
|
||||||
const performanceType = selectPerformanceSettings(global);
|
const performanceType = selectPerformanceSettings(global);
|
||||||
|
|
||||||
void oldSetLanguage(language, undefined, true);
|
void oldSetLanguage(language as LangCode, undefined, true);
|
||||||
|
|
||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { addCallback } from '../../../lib/teact/teactn';
|
import { addCallback } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import type { ActionReturnType, GlobalState } from '../../types';
|
import type { ActionReturnType, GlobalState } from '../../types';
|
||||||
import { SettingsScreens } from '../../../types';
|
import { type LangCode, SettingsScreens } from '../../../types';
|
||||||
|
|
||||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { disableDebugConsole, initDebugConsole } from '../../../util/debugConsole';
|
import { disableDebugConsole, initDebugConsole } from '../../../util/debugConsole';
|
||||||
@ -53,7 +53,7 @@ addCallback((global: GlobalState) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (settings.language !== prevSettings.language) {
|
if (settings.language !== prevSettings.language) {
|
||||||
oldSetLanguage(settings.language);
|
oldSetLanguage(settings.language as LangCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.timeFormat !== prevSettings.timeFormat) {
|
if (settings.timeFormat !== prevSettings.timeFormat) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type {
|
|||||||
ApiTopic,
|
ApiTopic,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
import type {
|
import type {
|
||||||
CustomPeer, NotifyException, NotifySettings, ThreadId,
|
CustomPeer, NotifyException, NotifySettings, ThreadId,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
@ -101,7 +101,7 @@ export function getPrivateChatUserId(chat: ApiChat) {
|
|||||||
return chat.id;
|
return chat.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getChatTitle(lang: LangFn, chat: ApiChat, isSelf = false) {
|
export function getChatTitle(lang: OldLangFn, chat: ApiChat, isSelf = false) {
|
||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
return lang('SavedMessages');
|
return lang('SavedMessages');
|
||||||
}
|
}
|
||||||
@ -247,7 +247,7 @@ export function getAllowedAttachmentOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageSendingRestrictionReason(
|
export function getMessageSendingRestrictionReason(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
currentUserBannedRights?: ApiChatBannedRights,
|
currentUserBannedRights?: ApiChatBannedRights,
|
||||||
defaultBannedRights?: ApiChatBannedRights,
|
defaultBannedRights?: ApiChatBannedRights,
|
||||||
) {
|
) {
|
||||||
@ -272,7 +272,7 @@ export function getMessageSendingRestrictionReason(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getForumComposerPlaceholder(
|
export function getForumComposerPlaceholder(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
chat?: ApiChat,
|
chat?: ApiChat,
|
||||||
threadId: ThreadId = MAIN_THREAD_ID,
|
threadId: ThreadId = MAIN_THREAD_ID,
|
||||||
topics?: Record<number, ApiTopic>,
|
topics?: Record<number, ApiTopic>,
|
||||||
@ -341,7 +341,7 @@ export function getCanDeleteChat(chat: ApiChat) {
|
|||||||
return isChatBasicGroup(chat) || ((isChatSuperGroup(chat) || isChatChannel(chat)) && chat.isCreator);
|
return isChatBasicGroup(chat) || ((isChatSuperGroup(chat) || isChatChannel(chat)) && chat.isCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFolderDescriptionText(lang: LangFn, folder: ApiChatFolder, chatsCount?: number) {
|
export function getFolderDescriptionText(lang: OldLangFn, folder: ApiChatFolder, chatsCount?: number) {
|
||||||
const {
|
const {
|
||||||
excludedChatIds, includedChatIds,
|
excludedChatIds, includedChatIds,
|
||||||
bots, groups, contacts, nonContacts, channels,
|
bots, groups, contacts, nonContacts, channels,
|
||||||
@ -376,7 +376,7 @@ export function getFolderDescriptionText(lang: LangFn, folder: ApiChatFolder, ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageSenderName(lang: LangFn, chatId: string, sender?: ApiPeer) {
|
export function getMessageSenderName(lang: OldLangFn, chatId: string, sender?: ApiPeer) {
|
||||||
if (!sender || isUserId(chatId)) {
|
if (!sender || isUserId(chatId)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -395,7 +395,7 @@ export function getMessageSenderName(lang: LangFn, chatId: string, sender?: ApiP
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function filterChatsByName(
|
export function filterChatsByName(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
chatIds: string[],
|
chatIds: string[],
|
||||||
chatsById: Record<string, ApiChat>,
|
chatsById: Record<string, ApiChat>,
|
||||||
query?: string,
|
query?: string,
|
||||||
@ -475,7 +475,7 @@ export function getIsSavedDialog(chatId: string, threadId: ThreadId | undefined,
|
|||||||
return chatId === currentUserId && threadId !== MAIN_THREAD_ID;
|
return chatId === currentUserId && threadId !== MAIN_THREAD_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGroupStatus(lang: LangFn, chat: ApiChat) {
|
export function getGroupStatus(lang: OldLangFn, chat: ApiChat) {
|
||||||
const chatTypeString = lang(getChatTypeString(chat));
|
const chatTypeString = lang(getChatTypeString(chat));
|
||||||
const { membersCount } = chat;
|
const { membersCount } = chat;
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { TeactNode } from '../../lib/teact/teact';
|
|||||||
import type {
|
import type {
|
||||||
ApiMediaExtendedPreview, ApiMessage, MediaContent, StatefulMediaContent,
|
ApiMediaExtendedPreview, ApiMessage, MediaContent, StatefulMediaContent,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
import { ApiMessageEntityTypes } from '../../api/types';
|
import { ApiMessageEntityTypes } from '../../api/types';
|
||||||
|
|
||||||
import { CONTENT_NOT_SUPPORTED } from '../../config';
|
import { CONTENT_NOT_SUPPORTED } from '../../config';
|
||||||
@ -17,7 +17,7 @@ const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
|
|||||||
export const TRUNCATED_SUMMARY_LENGTH = 80;
|
export const TRUNCATED_SUMMARY_LENGTH = 80;
|
||||||
|
|
||||||
export function getMessageSummaryText(
|
export function getMessageSummaryText(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
statefulContent: StatefulMediaContent | undefined,
|
statefulContent: StatefulMediaContent | undefined,
|
||||||
noEmoji = false,
|
noEmoji = false,
|
||||||
@ -106,12 +106,12 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getMediaContentTypeDescription(
|
export function getMediaContentTypeDescription(
|
||||||
lang: LangFn, content: MediaContent, statefulContent: StatefulMediaContent | undefined,
|
lang: OldLangFn, content: MediaContent, statefulContent: StatefulMediaContent | undefined,
|
||||||
) {
|
) {
|
||||||
return getSummaryDescription(lang, content, statefulContent);
|
return getSummaryDescription(lang, content, statefulContent);
|
||||||
}
|
}
|
||||||
export function getMessageSummaryDescription(
|
export function getMessageSummaryDescription(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
statefulContent: StatefulMediaContent | undefined,
|
statefulContent: StatefulMediaContent | undefined,
|
||||||
truncatedText?: string | TeactNode,
|
truncatedText?: string | TeactNode,
|
||||||
@ -120,7 +120,7 @@ export function getMessageSummaryDescription(
|
|||||||
return getSummaryDescription(lang, message.content, statefulContent, message, truncatedText, isExtended);
|
return getSummaryDescription(lang, message.content, statefulContent, message, truncatedText, isExtended);
|
||||||
}
|
}
|
||||||
function getSummaryDescription(
|
function getSummaryDescription(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
mediaContent: MediaContent,
|
mediaContent: MediaContent,
|
||||||
statefulContent: StatefulMediaContent | undefined,
|
statefulContent: StatefulMediaContent | undefined,
|
||||||
message?: ApiMessage,
|
message?: ApiMessage,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type {
|
|||||||
import type {
|
import type {
|
||||||
ApiPoll, MediaContainer, MediaContent, StatefulMediaContent,
|
ApiPoll, MediaContainer, MediaContent, StatefulMediaContent,
|
||||||
} from '../../api/types/messages';
|
} from '../../api/types/messages';
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
import type { ThreadId } from '../../types';
|
import type { ThreadId } from '../../types';
|
||||||
import type { GlobalState } from '../types';
|
import type { GlobalState } from '../types';
|
||||||
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
||||||
@ -208,7 +208,7 @@ export function isAnonymousOwnMessage(message: ApiMessage) {
|
|||||||
return Boolean(message.senderId) && !isUserId(message.senderId) && isOwnMessage(message);
|
return Boolean(message.senderId) && !isUserId(message.senderId) && isOwnMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSenderTitle(lang: LangFn, sender: ApiPeer) {
|
export function getSenderTitle(lang: OldLangFn, sender: ApiPeer) {
|
||||||
return isPeerUser(sender) ? getUserFullName(sender) : getChatTitle(lang, sender);
|
return isPeerUser(sender) ? getUserFullName(sender) : getChatTitle(lang, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,10 +344,10 @@ export function extractMessageText(message: ApiMessage | ApiStory, inChatList =
|
|||||||
return { text, entities };
|
return { text, entities };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExpiredMessageDescription(langFn: LangFn, message: ApiMessage): string | undefined {
|
export function getExpiredMessageDescription(langFn: OldLangFn, message: ApiMessage): string | undefined {
|
||||||
return getExpiredMessageContentDescription(langFn, message.content);
|
return getExpiredMessageContentDescription(langFn, message.content);
|
||||||
}
|
}
|
||||||
export function getExpiredMessageContentDescription(langFn: LangFn, mediaContent: MediaContent): string | undefined {
|
export function getExpiredMessageContentDescription(langFn: OldLangFn, mediaContent: MediaContent): string | undefined {
|
||||||
const { isExpiredVoice, isExpiredRoundVideo } = mediaContent;
|
const { isExpiredVoice, isExpiredRoundVideo } = mediaContent;
|
||||||
if (isExpiredVoice) {
|
if (isExpiredVoice) {
|
||||||
return langFn('Message.VoiceMessageExpired');
|
return langFn('Message.VoiceMessageExpired');
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ApiMessage } from '../../api/types';
|
import type { ApiMessage } from '../../api/types';
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import { renderMessageText } from '../../components/common/helpers/renderMessageText';
|
import { renderMessageText } from '../../components/common/helpers/renderMessageText';
|
||||||
import { getGlobal } from '..';
|
import { getGlobal } from '..';
|
||||||
@ -7,7 +7,7 @@ import { getMessageStatefulContent } from './messages';
|
|||||||
import { getMessageSummaryDescription, getMessageSummaryEmoji } from './messageSummary';
|
import { getMessageSummaryDescription, getMessageSummaryEmoji } from './messageSummary';
|
||||||
|
|
||||||
export function renderMessageSummaryHtml(
|
export function renderMessageSummaryHtml(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
) {
|
) {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ApiPeer, ApiUser, ApiUserStatus } from '../../api/types';
|
import type { ApiPeer, ApiUser, ApiUserStatus } from '../../api/types';
|
||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import { ANONYMOUS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
import { ANONYMOUS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||||
import { formatFullDate, formatTime } from '../../util/dates/dateFormat';
|
import { formatFullDate, formatTime } from '../../util/dates/dateFormat';
|
||||||
@ -66,7 +66,7 @@ export function getUserFullName(user?: ApiUser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getUserStatus(
|
export function getUserStatus(
|
||||||
lang: LangFn, user: ApiUser, userStatus: ApiUserStatus | undefined,
|
lang: OldLangFn, user: ApiUser, userStatus: ApiUserStatus | undefined,
|
||||||
) {
|
) {
|
||||||
if (user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
if (user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
||||||
return lang('ServiceNotifications');
|
return lang('ServiceNotifications');
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import type {
|
|||||||
ApiInputInvoiceStarGift,
|
ApiInputInvoiceStarGift,
|
||||||
ApiInputMessageReplyInfo,
|
ApiInputMessageReplyInfo,
|
||||||
ApiKeyboardButton,
|
ApiKeyboardButton,
|
||||||
|
ApiLanguage,
|
||||||
ApiMediaFormat,
|
ApiMediaFormat,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
@ -115,7 +116,6 @@ import type {
|
|||||||
InlineBotSettings,
|
InlineBotSettings,
|
||||||
ISettings,
|
ISettings,
|
||||||
IThemeSettings,
|
IThemeSettings,
|
||||||
LangCode,
|
|
||||||
LoadMoreDirection,
|
LoadMoreDirection,
|
||||||
ManagementProgress,
|
ManagementProgress,
|
||||||
ManagementScreens,
|
ManagementScreens,
|
||||||
@ -1199,7 +1199,7 @@ export type GlobalState = {
|
|||||||
defaultTopicIconsId?: string;
|
defaultTopicIconsId?: string;
|
||||||
defaultStatusIconsId?: string;
|
defaultStatusIconsId?: string;
|
||||||
premiumGifts?: ApiStickerSet;
|
premiumGifts?: ApiStickerSet;
|
||||||
emojiKeywords: Partial<Record<LangCode, EmojiKeywords>>;
|
emojiKeywords: Record<string, EmojiKeywords | undefined>;
|
||||||
|
|
||||||
gifs: {
|
gifs: {
|
||||||
saved: {
|
saved: {
|
||||||
@ -1243,6 +1243,7 @@ export type GlobalState = {
|
|||||||
notifyExceptions?: Record<number, NotifyException>;
|
notifyExceptions?: Record<number, NotifyException>;
|
||||||
lastPremiumBandwithNotificationDate?: number;
|
lastPremiumBandwithNotificationDate?: number;
|
||||||
paidReactionPrivacy?: boolean;
|
paidReactionPrivacy?: boolean;
|
||||||
|
languages?: ApiLanguage[];
|
||||||
};
|
};
|
||||||
|
|
||||||
push?: {
|
push?: {
|
||||||
@ -1377,7 +1378,7 @@ export interface ActionPayloads {
|
|||||||
faveSticker: { sticker: ApiSticker } & WithTabId;
|
faveSticker: { sticker: ApiSticker } & WithTabId;
|
||||||
unfaveSticker: { sticker: ApiSticker };
|
unfaveSticker: { sticker: ApiSticker };
|
||||||
toggleStickerSet: { stickerSetId: string };
|
toggleStickerSet: { stickerSetId: string };
|
||||||
loadEmojiKeywords: { language: LangCode };
|
loadEmojiKeywords: { language: string };
|
||||||
|
|
||||||
// groups
|
// groups
|
||||||
togglePreHistoryHidden: {
|
togglePreHistoryHidden: {
|
||||||
@ -1484,7 +1485,7 @@ export interface ActionPayloads {
|
|||||||
updateContentSettings: boolean;
|
updateContentSettings: boolean;
|
||||||
|
|
||||||
loadCountryList: {
|
loadCountryList: {
|
||||||
langCode?: LangCode;
|
langCode?: string;
|
||||||
};
|
};
|
||||||
ensureTimeFormat: WithTabId | undefined;
|
ensureTimeFormat: WithTabId | undefined;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { GlobalState } from '../global/types';
|
import type { GlobalState } from '../global/types';
|
||||||
import type { LangFn } from './useOldLang';
|
import type { OldLangFn } from './useOldLang';
|
||||||
|
|
||||||
import useBrowserOnline from './window/useBrowserOnline';
|
import useBrowserOnline from './window/useBrowserOnline';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ type ConnectionStatusPosition =
|
|||||||
| 'none';
|
| 'none';
|
||||||
|
|
||||||
export default function useConnectionStatus(
|
export default function useConnectionStatus(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
connectionState: GlobalState['connectionState'],
|
connectionState: GlobalState['connectionState'],
|
||||||
isSyncing: boolean | undefined,
|
isSyncing: boolean | undefined,
|
||||||
hasMiddleHeader: boolean,
|
hasMiddleHeader: boolean,
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import * as langProvider from '../util/oldLangProvider';
|
|||||||
import useEffectOnce from './useEffectOnce';
|
import useEffectOnce from './useEffectOnce';
|
||||||
import useForceUpdate from './useForceUpdate';
|
import useForceUpdate from './useForceUpdate';
|
||||||
|
|
||||||
export type LangFn = langProvider.LangFn;
|
export type OldLangFn = langProvider.LangFn;
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
const useOldLang = (): LangFn => {
|
const useOldLang = (): OldLangFn => {
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
|
|||||||
@ -73,6 +73,7 @@ class TelegramClient {
|
|||||||
systemVersion: undefined,
|
systemVersion: undefined,
|
||||||
appVersion: undefined,
|
appVersion: undefined,
|
||||||
langCode: 'en',
|
langCode: 'en',
|
||||||
|
langPack: 'weba',
|
||||||
systemLangCode: 'en',
|
systemLangCode: 'en',
|
||||||
baseLogger: 'gramjs',
|
baseLogger: 'gramjs',
|
||||||
useWSS: false,
|
useWSS: false,
|
||||||
@ -158,7 +159,7 @@ class TelegramClient {
|
|||||||
.toString() || '1.0',
|
.toString() || '1.0',
|
||||||
appVersion: args.appVersion || '1.0',
|
appVersion: args.appVersion || '1.0',
|
||||||
langCode: args.langCode,
|
langCode: args.langCode,
|
||||||
langPack: 'weba',
|
langPack: args.langPack,
|
||||||
systemLangCode: args.systemLangCode,
|
systemLangCode: args.systemLangCode,
|
||||||
query: x,
|
query: x,
|
||||||
proxy: undefined, // no proxies yet.
|
proxy: undefined, // no proxies yet.
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import type {
|
|||||||
ApiExportedInvite,
|
ApiExportedInvite,
|
||||||
ApiFakeType,
|
ApiFakeType,
|
||||||
ApiLabeledPrice,
|
ApiLabeledPrice,
|
||||||
ApiLanguage,
|
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
@ -113,8 +112,7 @@ export interface ISettings extends NotifySettings, Record<string, any> {
|
|||||||
shouldSuggestCustomEmoji: boolean;
|
shouldSuggestCustomEmoji: boolean;
|
||||||
shouldUpdateStickerSetOrder: boolean;
|
shouldUpdateStickerSetOrder: boolean;
|
||||||
hasPassword?: boolean;
|
hasPassword?: boolean;
|
||||||
languages?: ApiLanguage[];
|
language: string;
|
||||||
language: LangCode;
|
|
||||||
isSensitiveEnabled?: boolean;
|
isSensitiveEnabled?: boolean;
|
||||||
canChangeSensitive?: boolean;
|
canChangeSensitive?: boolean;
|
||||||
timeFormat: TimeFormat;
|
timeFormat: TimeFormat;
|
||||||
@ -481,8 +479,8 @@ export type NotifyException = {
|
|||||||
|
|
||||||
export type EmojiKeywords = {
|
export type EmojiKeywords = {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
version: number;
|
version?: number;
|
||||||
keywords: Record<string, string[]>;
|
keywords?: Record<string, string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InlineBotSettings = {
|
export type InlineBotSettings = {
|
||||||
|
|||||||
1123
src/types/language.d.ts
vendored
1123
src/types/language.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
import type { LangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
import type { TimeFormat } from '../../types';
|
import type { TimeFormat } from '../../types';
|
||||||
|
|
||||||
import withCache from '../withCache';
|
import withCache from '../withCache';
|
||||||
@ -39,7 +39,7 @@ function toIsoString(date: Date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @optimization `toLocaleTimeString` is avoided because of bad performance
|
// @optimization `toLocaleTimeString` is avoided because of bad performance
|
||||||
export function formatTime(lang: LangFn, datetime: number | Date) {
|
export function formatTime(lang: OldLangFn, datetime: number | Date) {
|
||||||
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
||||||
const timeFormat = lang.timeFormat || '24h';
|
const timeFormat = lang.timeFormat || '24h';
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ export function formatTime(lang: LangFn, datetime: number | Date) {
|
|||||||
return `${String(hours).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}${marker}`;
|
return `${String(hours).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}${marker}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatPastTimeShort(lang: LangFn, datetime: number | Date, alwaysShowTime = false) {
|
export function formatPastTimeShort(lang: OldLangFn, datetime: number | Date, alwaysShowTime = false) {
|
||||||
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
||||||
|
|
||||||
const time = formatTime(lang, date);
|
const time = formatTime(lang, date);
|
||||||
@ -76,16 +76,16 @@ export function formatPastTimeShort(lang: LangFn, datetime: number | Date, alway
|
|||||||
return alwaysShowTime ? lang('FullDateTimeFormat', [formattedDate, time]) : formattedDate;
|
return alwaysShowTime ? lang('FullDateTimeFormat', [formattedDate, time]) : formattedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatFullDate(lang: LangFn, datetime: number | Date) {
|
export function formatFullDate(lang: OldLangFn, datetime: number | Date) {
|
||||||
return formatDateToString(datetime, lang.code, false, 'numeric');
|
return formatDateToString(datetime, lang.code, false, 'numeric');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMonthAndYear(lang: LangFn, date: Date, isShort = false) {
|
export function formatMonthAndYear(lang: OldLangFn, date: Date, isShort = false) {
|
||||||
return formatDateToString(date, lang.code, false, isShort ? 'short' : 'long', true);
|
return formatDateToString(date, lang.code, false, isShort ? 'short' : 'long', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatCountdown(
|
export function formatCountdown(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
msLeft: number,
|
msLeft: number,
|
||||||
) {
|
) {
|
||||||
const days = Math.floor(msLeft / MILLISECONDS_IN_DAY);
|
const days = Math.floor(msLeft / MILLISECONDS_IN_DAY);
|
||||||
@ -104,7 +104,7 @@ export function formatCountdown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatCountdownShort(lang: LangFn, msLeft: number): string {
|
export function formatCountdownShort(lang: OldLangFn, msLeft: number): string {
|
||||||
if (msLeft < 60 * 1000) {
|
if (msLeft < 60 * 1000) {
|
||||||
return Math.ceil(msLeft / 1000).toString();
|
return Math.ceil(msLeft / 1000).toString();
|
||||||
} else if (msLeft < 60 * 60 * 1000) {
|
} else if (msLeft < 60 * 60 * 1000) {
|
||||||
@ -116,7 +116,7 @@ export function formatCountdownShort(lang: LangFn, msLeft: number): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatLastUpdated(lang: LangFn, currentTime: number, lastUpdated = currentTime) {
|
export function formatLastUpdated(lang: OldLangFn, currentTime: number, lastUpdated = currentTime) {
|
||||||
const seconds = currentTime - lastUpdated;
|
const seconds = currentTime - lastUpdated;
|
||||||
if (seconds < 60) {
|
if (seconds < 60) {
|
||||||
return lang('LiveLocationUpdated.JustNow');
|
return lang('LiveLocationUpdated.JustNow');
|
||||||
@ -127,7 +127,7 @@ export function formatLastUpdated(lang: LangFn, currentTime: number, lastUpdated
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatRelativeTime(lang: LangFn, currentTime: number, lastUpdated = currentTime) {
|
export function formatRelativeTime(lang: OldLangFn, currentTime: number, lastUpdated = currentTime) {
|
||||||
const seconds = currentTime - lastUpdated;
|
const seconds = currentTime - lastUpdated;
|
||||||
|
|
||||||
if (seconds < 60) {
|
if (seconds < 60) {
|
||||||
@ -156,7 +156,7 @@ export function formatRelativeTime(lang: LangFn, currentTime: number, lastUpdate
|
|||||||
|
|
||||||
type DurationType = 'Seconds' | 'Minutes' | 'Hours' | 'Days' | 'Weeks';
|
type DurationType = 'Seconds' | 'Minutes' | 'Hours' | 'Days' | 'Weeks';
|
||||||
|
|
||||||
export function formatTimeDuration(lang: LangFn, duration: number, showLast = 2) {
|
export function formatTimeDuration(lang: OldLangFn, duration: number, showLast = 2) {
|
||||||
if (!duration) {
|
if (!duration) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -196,7 +196,7 @@ export function formatTimeDuration(lang: LangFn, duration: number, showLast = 2)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatHumanDate(
|
export function formatHumanDate(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
datetime: number | Date,
|
datetime: number | Date,
|
||||||
isShort = false,
|
isShort = false,
|
||||||
noWeekdays = false,
|
noWeekdays = false,
|
||||||
@ -237,13 +237,13 @@ export function formatHumanDate(
|
|||||||
* Returns weekday name
|
* Returns weekday name
|
||||||
* @param day 0 - Sunday, 1 - Monday, ...
|
* @param day 0 - Sunday, 1 - Monday, ...
|
||||||
*/
|
*/
|
||||||
export function formatWeekday(lang: LangFn, day: number, isShort = false) {
|
export function formatWeekday(lang: OldLangFn, day: number, isShort = false) {
|
||||||
const weekDay = WEEKDAYS_FULL[day];
|
const weekDay = WEEKDAYS_FULL[day];
|
||||||
return isShort ? lang(`Weekday.Short${weekDay}`) : lang(`Weekday.${weekDay}`);
|
return isShort ? lang(`Weekday.Short${weekDay}`) : lang(`Weekday.${weekDay}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMediaDateTime(
|
export function formatMediaDateTime(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
datetime: number | Date,
|
datetime: number | Date,
|
||||||
isUpperFirst?: boolean,
|
isUpperFirst?: boolean,
|
||||||
) {
|
) {
|
||||||
@ -350,7 +350,7 @@ export function formatDateTimeToString(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatDateAtTime(
|
export function formatDateAtTime(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
datetime: number | Date,
|
datetime: number | Date,
|
||||||
) {
|
) {
|
||||||
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
||||||
@ -375,7 +375,7 @@ export function formatDateAtTime(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatDateInFuture(
|
export function formatDateInFuture(
|
||||||
lang: LangFn,
|
lang: OldLangFn,
|
||||||
currentTime: number,
|
currentTime: number,
|
||||||
datetime: number,
|
datetime: number,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React, { type TeactNode } from '../lib/teact/teact';
|
import React, { type TeactNode } from '../lib/teact/teact';
|
||||||
|
|
||||||
import type { LangCode } from '../types';
|
|
||||||
|
|
||||||
import { STARS_CURRENCY_CODE } from '../config';
|
import { STARS_CURRENCY_CODE } from '../config';
|
||||||
|
|
||||||
import StarIcon from '../components/common/icons/StarIcon';
|
import StarIcon from '../components/common/icons/StarIcon';
|
||||||
@ -9,7 +7,7 @@ import StarIcon from '../components/common/icons/StarIcon';
|
|||||||
export function formatCurrency(
|
export function formatCurrency(
|
||||||
totalPrice: number,
|
totalPrice: number,
|
||||||
currency: string,
|
currency: string,
|
||||||
locale: LangCode = 'en',
|
locale: string = 'en',
|
||||||
options?: {
|
options?: {
|
||||||
shouldOmitFractions?: boolean;
|
shouldOmitFractions?: boolean;
|
||||||
iconClassName?: string;
|
iconClassName?: string;
|
||||||
@ -27,7 +25,7 @@ export function formatCurrency(
|
|||||||
export function formatCurrencyAsString(
|
export function formatCurrencyAsString(
|
||||||
totalPrice: number,
|
totalPrice: number,
|
||||||
currency: string,
|
currency: string,
|
||||||
locale: LangCode = 'en',
|
locale: string = 'en',
|
||||||
options?: {
|
options?: {
|
||||||
shouldOmitFractions?: boolean;
|
shouldOmitFractions?: boolean;
|
||||||
},
|
},
|
||||||
|
|||||||
20
src/util/localization/format.tsx
Normal file
20
src/util/localization/format.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { LangFn } from './types';
|
||||||
|
|
||||||
|
import { STARS_ICON_PLACEHOLDER } from '../../config';
|
||||||
|
|
||||||
|
import StarIcon from '../../components/common/icons/StarIcon';
|
||||||
|
|
||||||
|
export function formatStarsAsText(lang: LangFn, amount: number) {
|
||||||
|
return lang('StarsAmountText', { amount }, { pluralValue: amount });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatStarsAsIcon(lang: LangFn, amount: number) {
|
||||||
|
return lang('StarsAmount', { amount }, {
|
||||||
|
withNodes: true,
|
||||||
|
specialReplacement: {
|
||||||
|
[STARS_ICON_PLACEHOLDER]: <StarIcon type="gold" className="star-amount-icon" size="adaptive" />,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -4,21 +4,23 @@ import type {
|
|||||||
ApiLanguage,
|
ApiLanguage,
|
||||||
CachedLangData,
|
CachedLangData,
|
||||||
LangPack,
|
LangPack,
|
||||||
|
LangPackStringValue,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { LangKey } from '../../types/language';
|
import type { LangKey, LangVariable } from '../../types/language';
|
||||||
import {
|
import {
|
||||||
type AdvancedLangFnOptions,
|
type AdvancedLangFnOptions,
|
||||||
|
type AdvancedLangFnOptionsWithPlural,
|
||||||
areAdvancedLangFnOptions,
|
areAdvancedLangFnOptions,
|
||||||
isDeletedLangString,
|
isDeletedLangString,
|
||||||
isPluralLangString,
|
isPluralLangString,
|
||||||
type LangFn,
|
type LangFn,
|
||||||
type LangFnOptions,
|
type LangFnOptions,
|
||||||
|
type LangFnOptionsWithPlural,
|
||||||
type LangFnParameters,
|
type LangFnParameters,
|
||||||
type LangFnWithFunction,
|
|
||||||
type LangFormatters,
|
type LangFormatters,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
import { DEBUG } from '../../config';
|
import { DEBUG, LANG_PACK } from '../../config';
|
||||||
import { callApi } from '../../api/gramjs';
|
import { callApi } from '../../api/gramjs';
|
||||||
import renderText, { type TextFilter } from '../../components/common/helpers/renderText';
|
import renderText, { type TextFilter } from '../../components/common/helpers/renderText';
|
||||||
import { MAIN_IDB_STORE } from '../browser/idb';
|
import { MAIN_IDB_STORE } from '../browser/idb';
|
||||||
@ -37,7 +39,6 @@ import LimitedMap from '../primitives/LimitedMap';
|
|||||||
|
|
||||||
import initialStrings from '../../assets/localization/initialStrings';
|
import initialStrings from '../../assets/localization/initialStrings';
|
||||||
|
|
||||||
const LANG_PACK = 'weba';
|
|
||||||
const LANGPACK_STORE_PREFIX = 'langpack-';
|
const LANGPACK_STORE_PREFIX = 'langpack-';
|
||||||
const FORMATTERS_FALLBACK_LANG = 'en';
|
const FORMATTERS_FALLBACK_LANG = 'en';
|
||||||
|
|
||||||
@ -115,14 +116,22 @@ async function fetchDifference() {
|
|||||||
langCode: langPack.langCode,
|
langCode: langPack.langCode,
|
||||||
fromVersion: langPack.version,
|
fromVersion: langPack.version,
|
||||||
});
|
});
|
||||||
if (!result || result.version === langPack.version) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
applyLangPackDifference(result.version, result.strings, result.keysToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyLangPackDifference(
|
||||||
|
version: number, strings: Record<string, LangPackStringValue>, keysToRemove: string[],
|
||||||
|
) {
|
||||||
|
if (!langPack || !language || version === langPack.version) return;
|
||||||
|
|
||||||
const newLangPack = {
|
const newLangPack = {
|
||||||
...langPack,
|
...langPack,
|
||||||
version: result.version,
|
version,
|
||||||
strings: {
|
strings: {
|
||||||
...omit(langPack.strings, result.keysToRemove),
|
...omit(langPack.strings, keysToRemove),
|
||||||
...result.strings,
|
...strings,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
updateLangPack(newLangPack);
|
updateLangPack(newLangPack);
|
||||||
@ -242,6 +251,11 @@ export async function loadAndChangeLanguage(langCode: string, shouldCheckCache?:
|
|||||||
return changeLanguage(remoteLanguage);
|
return changeLanguage(remoteLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function requestLangPackDifference(langCode: string) {
|
||||||
|
if (language?.langCode !== langCode) return;
|
||||||
|
fetchDifference();
|
||||||
|
}
|
||||||
|
|
||||||
export async function changeLanguage(newLanguage: ApiLanguage) {
|
export async function changeLanguage(newLanguage: ApiLanguage) {
|
||||||
if (langPack && language?.langCode === newLanguage.langCode) return;
|
if (langPack && language?.langCode === newLanguage.langCode) return;
|
||||||
|
|
||||||
@ -300,10 +314,10 @@ function createTranslationFn(): LangFn {
|
|||||||
fn.pluralCode = language?.pluralCode || FORMATTERS_FALLBACK_LANG;
|
fn.pluralCode = language?.pluralCode || FORMATTERS_FALLBACK_LANG;
|
||||||
fn.with = (({ key, variables, options }: LangFnParameters) => {
|
fn.with = (({ key, variables, options }: LangFnParameters) => {
|
||||||
if (options && areAdvancedLangFnOptions(options)) {
|
if (options && areAdvancedLangFnOptions(options)) {
|
||||||
return processTranslationAdvanced(key, variables as Record<string, TeactNode>, options);
|
return processTranslationAdvanced(key, variables as Record<string, TeactNode | undefined>, options);
|
||||||
}
|
}
|
||||||
return processTranslation(key, variables as Record<string, string | number>, options);
|
return processTranslation(key, variables as Record<string, LangVariable>, options);
|
||||||
}) as LangFnWithFunction;
|
});
|
||||||
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(', ');
|
||||||
@ -315,7 +329,7 @@ export function getTranslationFn(): LangFn {
|
|||||||
return translationFn;
|
return translationFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getString(langKey: LangKey, count: number, options?: Pick<LangFnOptions, 'pluralValue'>) {
|
function getString(langKey: LangKey, count: number) {
|
||||||
let langPackStringValue = langPack?.strings[langKey];
|
let langPackStringValue = langPack?.strings[langKey];
|
||||||
|
|
||||||
if (!langPackStringValue && !fallbackLangPack) {
|
if (!langPackStringValue && !fallbackLangPack) {
|
||||||
@ -327,7 +341,7 @@ function getString(langKey: LangKey, count: number, options?: Pick<LangFnOptions
|
|||||||
|
|
||||||
if (!langPackStringValue || isDeletedLangString(langPackStringValue)) return undefined;
|
if (!langPackStringValue || isDeletedLangString(langPackStringValue)) return undefined;
|
||||||
|
|
||||||
const pluralSuffix = formatters?.pluralRules.select(options?.pluralValue || count) || 'other';
|
const pluralSuffix = formatters?.pluralRules.select(count) || 'other';
|
||||||
|
|
||||||
const string = isPluralLangString(langPackStringValue)
|
const string = isPluralLangString(langPackStringValue)
|
||||||
? (langPackStringValue[pluralSuffix] || langPackStringValue.other)
|
? (langPackStringValue[pluralSuffix] || langPackStringValue.other)
|
||||||
@ -337,20 +351,26 @@ function getString(langKey: LangKey, count: number, options?: Pick<LangFnOptions
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processTranslation(
|
function processTranslation(
|
||||||
langKey: LangKey, variables?: Record<string, string | number>, options?: LangFnOptions,
|
langKey: LangKey,
|
||||||
|
variables?: Record<string, LangVariable>,
|
||||||
|
options?: LangFnOptions | LangFnOptionsWithPlural,
|
||||||
): string {
|
): string {
|
||||||
const cacheKey = `${langKey}-${JSON.stringify(variables)}-${JSON.stringify(options)}`;
|
const cacheKey = `${langKey}-${JSON.stringify(variables)}-${JSON.stringify(options)}`;
|
||||||
if (TRANSLATION_CACHE.has(cacheKey)) {
|
if (TRANSLATION_CACHE.has(cacheKey)) {
|
||||||
return TRANSLATION_CACHE.get(cacheKey)!;
|
return TRANSLATION_CACHE.get(cacheKey)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string = getString(langKey, options?.pluralValue || Number(variables?.count) || 0, options);
|
const pluralValue = options && 'pluralValue' in options ? Number(options.pluralValue) : 0;
|
||||||
|
const string = getString(langKey, pluralValue);
|
||||||
|
|
||||||
if (!string) return langKey;
|
if (!string) return langKey;
|
||||||
|
|
||||||
const variableEntries = variables ? Object.entries(variables) : [];
|
const variableEntries = variables ? Object.entries(variables) : [];
|
||||||
const finalString = variableEntries.reduce((result, [key, value]) => {
|
const finalString = variableEntries.reduce((result, [key, value]) => {
|
||||||
return result.replace(`{${key}}`, String(value));
|
if (!value) return result;
|
||||||
|
|
||||||
|
const valueAsString = Number.isInteger(value) ? formatters!.number.format(value as number) : String(value);
|
||||||
|
return result.replace(`{${key}}`, valueAsString);
|
||||||
}, string);
|
}, string);
|
||||||
|
|
||||||
TRANSLATION_CACHE.set(cacheKey, finalString);
|
TRANSLATION_CACHE.set(cacheKey, finalString);
|
||||||
@ -359,9 +379,12 @@ function processTranslation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processTranslationAdvanced(
|
function processTranslationAdvanced(
|
||||||
langKey: LangKey, variables?: Record<string, TeactNode>, options?: AdvancedLangFnOptions,
|
langKey: LangKey,
|
||||||
|
variables?: Record<string, TeactNode | undefined>,
|
||||||
|
options?: AdvancedLangFnOptions | AdvancedLangFnOptionsWithPlural,
|
||||||
): TeactNode {
|
): TeactNode {
|
||||||
const string = getString(langKey, options?.pluralValue || Number(variables?.count) || 0, options);
|
const pluralValue = options && 'pluralValue' in options ? Number(options.pluralValue) : 0;
|
||||||
|
const string = getString(langKey, pluralValue);
|
||||||
if (!string) return langKey;
|
if (!string) return langKey;
|
||||||
|
|
||||||
const variableEntries = variables ? Object.entries(variables) : [];
|
const variableEntries = variables ? Object.entries(variables) : [];
|
||||||
@ -389,7 +412,10 @@ function processTranslationAdvanced(
|
|||||||
return renderText(curr, filters, {
|
return renderText(curr, filters, {
|
||||||
markdownPostProcessor: (part: string) => {
|
markdownPostProcessor: (part: string) => {
|
||||||
return variableEntries.reduce((result, [key, value]): TeactNode[] => {
|
return variableEntries.reduce((result, [key, value]): TeactNode[] => {
|
||||||
return replaceInStringsWithTeact(result, `{${key}}`, value);
|
if (!value) return result;
|
||||||
|
|
||||||
|
const preparedValue = Number.isInteger(value) ? formatters!.number.format(value as number) : value;
|
||||||
|
return replaceInStringsWithTeact(result, `{${key}}`, preparedValue);
|
||||||
}, [part] as TeactNode[]);
|
}, [part] as TeactNode[]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -397,7 +423,10 @@ function processTranslationAdvanced(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return variableEntries.reduce((result, [key, value]): TeactNode[] => {
|
return variableEntries.reduce((result, [key, value]): TeactNode[] => {
|
||||||
return replaceInStringsWithTeact(result, `{${key}}`, value);
|
if (!value) return result;
|
||||||
|
|
||||||
|
const preparedValue = Number.isInteger(value) ? formatters!.number.format(value as number) : value;
|
||||||
|
return replaceInStringsWithTeact(result, `{${key}}`, preparedValue);
|
||||||
}, tempResult);
|
}, tempResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,30 +7,51 @@ import type {
|
|||||||
LangPackStringValueRegular,
|
LangPackStringValueRegular,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { TextFilter } from '../../components/common/helpers/renderText';
|
import type { TextFilter } from '../../components/common/helpers/renderText';
|
||||||
import type { LangKey, LangPair } from '../../types/language';
|
import type {
|
||||||
|
LangPairPluralWithVariables,
|
||||||
|
LangPairWithVariables,
|
||||||
|
PluralLangKey,
|
||||||
|
PluralLangKeyWithVariables,
|
||||||
|
RegularLangKey,
|
||||||
|
RegularLangKeyWithVariables,
|
||||||
|
} from '../../types/language';
|
||||||
|
|
||||||
type ReplaceTypeValues<T, R> = {
|
export interface LangFnOptionsRegular {
|
||||||
[K in keyof T]: R;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface LangFnOptions {
|
|
||||||
pluralValue?: number;
|
|
||||||
withNodes?: never;
|
withNodes?: never;
|
||||||
|
withMarkdown?: never;
|
||||||
|
pluralValue?: never;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdvancedLangFnOptions {
|
export type LangFnOptionsWithPlural = Omit<LangFnOptionsRegular, 'pluralValue'> & {
|
||||||
pluralValue?: number;
|
pluralValue: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LangFnOptions = LangFnOptionsRegular | LangFnOptionsWithPlural;
|
||||||
|
|
||||||
|
export interface AdvancedLangFnOptionsRegular {
|
||||||
withNodes: true;
|
withNodes: true;
|
||||||
withMarkdown?: boolean;
|
withMarkdown?: boolean;
|
||||||
|
pluralValue?: never;
|
||||||
renderTextFilters?: TextFilter[];
|
renderTextFilters?: TextFilter[];
|
||||||
specialReplacement?: Record<string, TeactNode>;
|
specialReplacement?: Record<string, TeactNode>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LangPairWithNodes = {
|
export type AdvancedLangFnOptionsWithPlural = Omit<AdvancedLangFnOptionsRegular, 'pluralValue'> & {
|
||||||
[K in keyof LangPair]: LangPair[K] extends object ? ReplaceTypeValues<LangPair[K], TeactNode> : LangPair[K];
|
pluralValue: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RegularLangFnParameters<T = LangPair> = {
|
export type AdvancedLangFnOptions = AdvancedLangFnOptionsRegular | AdvancedLangFnOptionsWithPlural;
|
||||||
|
|
||||||
|
type LangPairWithNodes = LangPairWithVariables<TeactNode | undefined>;
|
||||||
|
type LangPairPluralWithNodes = LangPairPluralWithVariables<TeactNode | undefined>;
|
||||||
|
|
||||||
|
type RegularLangFnParametersWithoutVariables = {
|
||||||
|
key: RegularLangKey;
|
||||||
|
variables?: undefined;
|
||||||
|
options?: LangFnOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RegularLangFnParametersWithVariables<T = LangPairWithVariables> = {
|
||||||
[K in keyof T]: {
|
[K in keyof T]: {
|
||||||
key: K;
|
key: K;
|
||||||
variables: T[K];
|
variables: T[K];
|
||||||
@ -38,7 +59,33 @@ type RegularLangFnParameters<T = LangPair> = {
|
|||||||
}
|
}
|
||||||
}[keyof T];
|
}[keyof T];
|
||||||
|
|
||||||
type AdvancedLangFnParameters<T = LangPairWithNodes> = {
|
type RegularLangFnPluralParameters = {
|
||||||
|
key: PluralLangKey;
|
||||||
|
variables?: undefined;
|
||||||
|
options: LangFnOptionsWithPlural;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RegularLangFnPluralParametersWithVariables<T = LangPairPluralWithVariables> = {
|
||||||
|
[K in keyof T]: {
|
||||||
|
key: K;
|
||||||
|
variables: T[K];
|
||||||
|
options: LangFnOptionsWithPlural;
|
||||||
|
}
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
type RegularLangFnParameters =
|
||||||
|
| RegularLangFnParametersWithoutVariables
|
||||||
|
| RegularLangFnParametersWithVariables
|
||||||
|
| RegularLangFnPluralParameters
|
||||||
|
| RegularLangFnPluralParametersWithVariables;
|
||||||
|
|
||||||
|
type AdvancedLangFnParametersWithoutVariables = {
|
||||||
|
key: RegularLangKey;
|
||||||
|
variables?: undefined;
|
||||||
|
options: AdvancedLangFnOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AdvancedLangFnParametersWithVariables<T = LangPairWithNodes> = {
|
||||||
[K in keyof T]: {
|
[K in keyof T]: {
|
||||||
key: K;
|
key: K;
|
||||||
variables: T[K];
|
variables: T[K];
|
||||||
@ -46,21 +93,56 @@ type AdvancedLangFnParameters<T = LangPairWithNodes> = {
|
|||||||
}
|
}
|
||||||
}[keyof T];
|
}[keyof T];
|
||||||
|
|
||||||
export type LangFnParameters = RegularLangFnParameters | AdvancedLangFnParameters;
|
type AdvancedLangFnPluralParameters = {
|
||||||
|
key: PluralLangKey;
|
||||||
export type LangFnWithFunction = {
|
variables?: undefined;
|
||||||
(params: RegularLangFnParameters): string;
|
options: AdvancedLangFnOptionsWithPlural;
|
||||||
(params: AdvancedLangFnParameters): TeactNode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AdvancedLangFnPluralParametersWithVariables<T = LangPairPluralWithNodes> = {
|
||||||
|
[K in keyof T]: {
|
||||||
|
key: K;
|
||||||
|
variables: T[K];
|
||||||
|
options: AdvancedLangFnOptionsWithPlural;
|
||||||
|
}
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
type AdvancedLangFnParameters =
|
||||||
|
| AdvancedLangFnParametersWithoutVariables
|
||||||
|
| AdvancedLangFnParametersWithVariables
|
||||||
|
| AdvancedLangFnPluralParameters
|
||||||
|
| AdvancedLangFnPluralParametersWithVariables;
|
||||||
|
|
||||||
|
export type LangFnParameters = RegularLangFnParameters | AdvancedLangFnParameters;
|
||||||
|
|
||||||
export type LangFn = {
|
export type LangFn = {
|
||||||
<K extends LangKey = LangKey, V extends LangPair[K] = LangPair[K]>(
|
<K = RegularLangKey>(
|
||||||
key: K, variables?: V, options?: LangFnOptions,
|
key: K, variables?: undefined, options?: LangFnOptions,
|
||||||
): string;
|
): string;
|
||||||
<K extends LangKey = LangKey, V extends LangPairWithNodes[K] = LangPairWithNodes[K]>(
|
<K = PluralLangKey>(
|
||||||
|
key: K, variables: undefined, options: LangFnOptionsWithPlural,
|
||||||
|
): string;
|
||||||
|
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
||||||
|
key: K, variables: V, options?: LangFnOptions,
|
||||||
|
): string;
|
||||||
|
<K extends PluralLangKeyWithVariables = PluralLangKeyWithVariables, V = LangPairPluralWithVariables[K]>(
|
||||||
|
key: K, variables: V, options: LangFnOptionsWithPlural,
|
||||||
|
): string;
|
||||||
|
|
||||||
|
<K = RegularLangKey>(
|
||||||
|
key: K, variables?: undefined, options?: AdvancedLangFnOptions,
|
||||||
|
): TeactNode;
|
||||||
|
<K = PluralLangKey>(
|
||||||
|
key: K, variables: undefined, options: AdvancedLangFnOptionsWithPlural,
|
||||||
|
): TeactNode;
|
||||||
|
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
||||||
key: K, variables: V, options: AdvancedLangFnOptions,
|
key: K, variables: V, options: AdvancedLangFnOptions,
|
||||||
): TeactNode;
|
): TeactNode;
|
||||||
with: LangFnWithFunction;
|
<K extends PluralLangKeyWithVariables = PluralLangKeyWithVariables, V = LangPairPluralWithVariables[K]>(
|
||||||
|
key: K, variables: V, options: AdvancedLangFnOptionsWithPlural,
|
||||||
|
): TeactNode;
|
||||||
|
|
||||||
|
with: (params: LangFnParameters) => 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;
|
||||||
@ -101,5 +183,5 @@ export function isLangFnParam(object: unknown): object is LangFnParameters {
|
|||||||
export function areAdvancedLangFnOptions(
|
export function areAdvancedLangFnOptions(
|
||||||
params: LangFnOptions | AdvancedLangFnOptions,
|
params: LangFnOptions | AdvancedLangFnOptions,
|
||||||
): params is AdvancedLangFnOptions {
|
): params is AdvancedLangFnOptions {
|
||||||
return 'withNodes' in params;
|
return 'withNodes' in params && Boolean(params.withNodes);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { ApiOldLangPack, ApiOldLangString } from '../api/types';
|
|||||||
import type { LangCode, TimeFormat } from '../types';
|
import type { LangCode, TimeFormat } from '../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, LANG_CACHE_NAME, LANG_PACKS,
|
DEFAULT_LANG_CODE, LANG_CACHE_NAME, LANG_PACKS, OLD_DEFAULT_LANG_PACK,
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { callApi } from '../api/gramjs';
|
import { callApi } from '../api/gramjs';
|
||||||
import * as cacheApi from './cacheApi';
|
import * as cacheApi from './cacheApi';
|
||||||
@ -155,14 +155,14 @@ export async function getTranslationForLangString(langCode: string, key: string)
|
|||||||
let translateString: ApiOldLangString | undefined;
|
let translateString: ApiOldLangString | undefined;
|
||||||
const cachedValue = await cacheApi.fetch(
|
const cachedValue = await cacheApi.fetch(
|
||||||
LANG_CACHE_NAME,
|
LANG_CACHE_NAME,
|
||||||
`${DEFAULT_LANG_PACK}_${langCode}_${key}`,
|
`${OLD_DEFAULT_LANG_PACK}_${langCode}_${key}`,
|
||||||
cacheApi.Type.Json,
|
cacheApi.Type.Json,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cachedValue) {
|
if (cachedValue) {
|
||||||
translateString = cachedValue.value;
|
translateString = cachedValue.value;
|
||||||
} else {
|
} else {
|
||||||
translateString = await fetchRemoteString(DEFAULT_LANG_PACK, langCode, key);
|
translateString = await fetchRemoteString(OLD_DEFAULT_LANG_PACK, langCode, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return processTranslation(translateString, key);
|
return processTranslation(translateString, key);
|
||||||
@ -199,7 +199,9 @@ export async function oldSetLanguage(langCode: LangCode, callback?: NoneToVoidFu
|
|||||||
langPack = newLangPack;
|
langPack = newLangPack;
|
||||||
document.documentElement.lang = langCode;
|
document.documentElement.lang = langCode;
|
||||||
|
|
||||||
const { languages, timeFormat } = getGlobal().settings.byKey;
|
const global = getGlobal();
|
||||||
|
const { languages, byKey } = global.settings;
|
||||||
|
const timeFormat = byKey?.timeFormat;
|
||||||
const langInfo = languages?.find((lang) => lang.langCode === langCode);
|
const langInfo = languages?.find((lang) => lang.langCode === langCode);
|
||||||
translationFn = createLangFn();
|
translationFn = createLangFn();
|
||||||
translationFn.isRtl = Boolean(langInfo?.isRtl);
|
translationFn.isRtl = Boolean(langInfo?.isRtl);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { LangFn } from '../hooks/useOldLang';
|
import type { OldLangFn } from '../hooks/useOldLang';
|
||||||
|
|
||||||
import EMOJI_REGEX from '../lib/twemojiRegex';
|
import EMOJI_REGEX from '../lib/twemojiRegex';
|
||||||
import fixNonStandardEmoji from './emoji/fixNonStandardEmoji';
|
import fixNonStandardEmoji from './emoji/fixNonStandardEmoji';
|
||||||
@ -48,7 +48,7 @@ export const getFirstLetters = withCache((phrase: string, count = 2) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB'];
|
const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB'];
|
||||||
export function formatFileSize(lang: LangFn, bytes: number, decimals = 1): string {
|
export function formatFileSize(lang: OldLangFn, bytes: number, decimals = 1): string {
|
||||||
if (bytes === 0) {
|
if (bytes === 0) {
|
||||||
return lang('FileSize.B', 0);
|
return lang('FileSize.B', 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user