From 18734bc5aa8895425520add9d5f1415ceccd813d Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Mon, 22 Mar 2021 22:38:03 +0400 Subject: [PATCH] Temp language commit --- src/components/appNavigationController.ts | 2 +- src/components/chat/input.ts | 23 ++- src/components/chat/markupTooltip.ts | 14 +- src/components/chat/selection.ts | 15 ++ src/components/checkboxField.ts | 8 +- src/components/radioField.ts | 5 +- src/components/row.ts | 17 ++- src/components/sidebarLeft/index.ts | 7 +- src/components/sidebarLeft/tabs/background.ts | 8 +- .../sidebarLeft/tabs/chatFolders.ts | 55 ++++--- src/components/sidebarLeft/tabs/editFolder.ts | 19 ++- .../sidebarLeft/tabs/editProfile.ts | 16 +- .../sidebarLeft/tabs/generalSettings.ts | 47 +++--- .../sidebarLeft/tabs/includedChats.ts | 12 +- .../sidebarLeft/tabs/notifications.ts | 26 ++-- .../sidebarLeft/tabs/privacyAndSecurity.ts | 52 ++++--- src/lang.ts | 140 ++++++++++++++++++ src/lib/appManagers/appImManager.ts | 20 +-- src/lib/langPack.ts | 96 ++++++------ 19 files changed, 390 insertions(+), 192 deletions(-) create mode 100644 src/lang.ts diff --git a/src/components/appNavigationController.ts b/src/components/appNavigationController.ts index c5989ef3..ef514e53 100644 --- a/src/components/appNavigationController.ts +++ b/src/components/appNavigationController.ts @@ -5,7 +5,7 @@ import { logger } from "../lib/logger"; import { doubleRaf } from "../helpers/schedulers"; export type NavigationItem = { - type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg', + type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg' | 'multiselect' | 'input-helper' | 'markup', onPop: (canAnimate: boolean) => boolean | void, onEscape?: () => boolean, noHistory?: boolean, diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index d61be16f..34154ed7 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -36,6 +36,8 @@ import rootScope from '../../lib/rootScope'; import PopupPinMessage from '../popups/unpinMessage'; import { debounce } from '../../helpers/schedulers'; import { tsNow } from '../../helpers/date'; +import appNavigationController from '../appNavigationController'; +import { isMobile } from '../../helpers/userAgent'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; @@ -1159,8 +1161,10 @@ export default class ChatInput { } }; - private onHelperCancel = (e: Event) => { - cancelEvent(e); + private onHelperCancel = (e?: Event) => { + if(e) { + cancelEvent(e); + } if(this.willSendWebPage) { const lastUrl = this.lastUrl; @@ -1458,7 +1462,11 @@ export default class ChatInput { this.forwardingFromPeerId = 0; this.editMsgId = undefined; this.helperType = this.helperFunc = undefined; - this.chat.container.classList.remove('is-helper-active'); + + if(this.chat.container.classList.contains('is-helper-active')) { + appNavigationController.removeByType('input-helper'); + this.chat.container.classList.remove('is-helper-active'); + } } public setInputValue(value: string, clear = true, focus = true) { @@ -1488,6 +1496,15 @@ export default class ChatInput { scroll.scrollTo(scroll.scrollHeight, 'top', true, true, 200); } */ + if(!isMobile) { + appNavigationController.pushItem({ + type: 'input-helper', + onPop: () => { + this.onHelperCancel(); + } + }); + } + if(input !== undefined) { this.setInputValue(input); } diff --git a/src/components/chat/markupTooltip.ts b/src/components/chat/markupTooltip.ts index 6ce78b0f..b4b0986c 100644 --- a/src/components/chat/markupTooltip.ts +++ b/src/components/chat/markupTooltip.ts @@ -4,7 +4,8 @@ import RichTextProcessor from "../../lib/richtextprocessor"; import ButtonIcon from "../buttonIcon"; import { clamp } from "../../helpers/number"; import { isTouchSupported } from "../../helpers/touchSupport"; -import { isApple } from "../../helpers/userAgent"; +import { isApple, isMobile } from "../../helpers/userAgent"; +import appNavigationController from "../appNavigationController"; //import { logger } from "../../lib/logger"; export default class MarkupTooltip { @@ -185,6 +186,8 @@ export default class MarkupTooltip { document.removeEventListener('mouseup', this.onMouseUpSingle); this.waitingForMouseUp = false; + appNavigationController.removeByType('markup'); + if(this.hideTimeout) clearTimeout(this.hideTimeout); this.hideTimeout = window.setTimeout(() => { this.hideTimeout = undefined; @@ -295,6 +298,15 @@ export default class MarkupTooltip { this.container.classList.add('is-visible'); + if(!isMobile) { + appNavigationController.pushItem({ + type: 'markup', + onPop: () => { + this.hide(); + } + }); + } + //this.log('selection', selectionRect, activeButton); } diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts index aa0c87f4..5fd42ea2 100644 --- a/src/components/chat/selection.ts +++ b/src/components/chat/selection.ts @@ -13,6 +13,8 @@ import { toast } from "../toast"; import SetTransition from "../singleTransition"; import ListenerSetter from "../../helpers/listenerSetter"; import PopupSendNow from "../popups/sendNow"; +import appNavigationController from "../appNavigationController"; +import { isMobileSafari } from "../../helpers/userAgent"; const MAX_SELECTION_LENGTH = 100; //const MIN_CLICK_MOVE = 32; // minimum bubble height @@ -315,6 +317,19 @@ export default class ChatSelection { }); }); + if(!isMobileSafari) { + if(forwards) { + appNavigationController.pushItem({ + type: 'multiselect', + onPop: () => { + this.cancelSelection(); + } + }); + } else { + appNavigationController.removeByType('multiselect'); + } + } + //const chatInput = this.appImManager.chatInput; if(this.isSelecting) { diff --git a/src/components/checkboxField.ts b/src/components/checkboxField.ts index ee29439a..81c5b067 100644 --- a/src/components/checkboxField.ts +++ b/src/components/checkboxField.ts @@ -1,6 +1,7 @@ import appStateManager from "../lib/appManagers/appStateManager"; import { getDeepProperty } from "../helpers/object"; import { ripple } from "./ripple"; +import { LangPackKey, _i18n } from "../lib/langPack"; export default class CheckboxField { public input: HTMLInputElement; @@ -8,7 +9,7 @@ export default class CheckboxField { public span: HTMLSpanElement; constructor(options: { - text?: string, + text?: LangPackKey, name?: string, round?: boolean, stateKey?: string, @@ -56,10 +57,7 @@ export default class CheckboxField { if(options.text) { span = this.span = document.createElement('span'); span.classList.add('checkbox-caption'); - - if(options.text) { - span.innerText = options.text; - } + _i18n(span, options.text); } else { label.classList.add('checkbox-without-caption'); } diff --git a/src/components/radioField.ts b/src/components/radioField.ts index a4b5888f..12fbd6ab 100644 --- a/src/components/radioField.ts +++ b/src/components/radioField.ts @@ -1,5 +1,6 @@ import appStateManager from "../lib/appManagers/appStateManager"; import { getDeepProperty } from "../helpers/object"; +import { LangPackKey, _i18n } from "../lib/langPack"; export default class RadioField { public input: HTMLInputElement; @@ -7,7 +8,7 @@ export default class RadioField { public main: HTMLElement; constructor(options: { - text?: string, + text?: LangPackKey, name: string, value?: string, stateKey?: string @@ -37,7 +38,7 @@ export default class RadioField { main.classList.add('radio-field-main'); if(options.text) { - main.innerHTML = options.text; + _i18n(main, options.text); /* const caption = document.createElement('div'); caption.classList.add('radio-field-main-caption'); caption.innerHTML = text; diff --git a/src/components/row.ts b/src/components/row.ts index dfaf055e..84581f5f 100644 --- a/src/components/row.ts +++ b/src/components/row.ts @@ -3,6 +3,7 @@ import RadioField from "./radioField"; import { ripple } from "./ripple"; import { SliderSuperTab } from "./slider"; import RadioForm from "./radioForm"; +import { LangPackKey, _i18n } from "../lib/langPack"; export default class Row { public container: HTMLElement; @@ -17,10 +18,12 @@ export default class Row { constructor(options: Partial<{ icon: string, subtitle: string, + subtitleLangKey: LangPackKey radioField: Row['radioField'], checkboxField: Row['checkboxField'], noCheckboxSubtitle: boolean, title: string, + titleLangKey: LangPackKey, titleRight: string, clickable: boolean | ((e: Event) => void), navigationTab: SliderSuperTab @@ -32,6 +35,8 @@ export default class Row { this.subtitle.classList.add('row-subtitle'); if(options.subtitle) { this.subtitle.innerHTML = options.subtitle; + } else if(options.subtitleLangKey) { + _i18n(this.subtitle, options.subtitleLangKey); } let havePadding = false; @@ -48,7 +53,7 @@ export default class Row { if(!options.noCheckboxSubtitle) { this.checkboxField.input.addEventListener('change', () => { - this.subtitle.innerHTML = this.checkboxField.input.checked ? 'Enabled' : 'Disabled'; + _i18n(this.subtitle, this.checkboxField.input.checked ? 'Checkbox.Enabled' : 'Checkbox.Disabled'); }); } } @@ -56,7 +61,7 @@ export default class Row { const i = options.radioField || options.checkboxField; i.label.classList.add('disable-hover'); } else { - if(options.title) { + if(options.title || options.titleLangKey) { let c: HTMLElement; if(options.titleRight) { c = document.createElement('div'); @@ -68,7 +73,11 @@ export default class Row { this.title = document.createElement('div'); this.title.classList.add('row-title'); - this.title.innerHTML = options.title; + if(options.title) { + this.title.innerHTML = options.title; + } else { + _i18n(this.title, options.titleLangKey); + } c.append(this.title); if(options.titleRight) { @@ -118,4 +127,4 @@ export default class Row { export const RadioFormFromRows = (rows: Row[], onChange: (value: string) => void) => { return RadioForm(rows.map(r => ({container: r.container, input: r.radioField.input})), onChange); -}; \ No newline at end of file +}; diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index a7d79d75..c10a27de 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -24,7 +24,6 @@ import AppContactsTab from "./tabs/contacts"; import AppArchivedTab from "./tabs/archivedTab"; import AppAddMembersTab from "./tabs/addMembers"; import { i18n_, LangPackKey } from "../../lib/langPack"; -import ButtonMenuToggle from "../buttonMenuToggle"; import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu"; export const LEFT_COLUMN_ACTIVE_CLASSNAME = 'is-left-column-shown'; @@ -84,7 +83,7 @@ export class AppSidebarLeft extends SidebarSlider { const btnArchive: ButtonMenuItemOptions = { icon: 'archive', - text: 'Archived', + text: 'ChatList.Menu.Archived', onClick: () => { new AppArchivedTab(this).open(); } @@ -92,7 +91,7 @@ export class AppSidebarLeft extends SidebarSlider { const btnMenu = ButtonMenu([{ icon: 'newgroup', - text: 'New Group', + text: 'NewGroup', onClick: onNewGroupClick }, { icon: 'user', @@ -114,7 +113,7 @@ export class AppSidebarLeft extends SidebarSlider { } }, { icon: 'help btn-disabled', - text: 'Help', + text: 'SettingsHelp', onClick: () => { } diff --git a/src/components/sidebarLeft/tabs/background.ts b/src/components/sidebarLeft/tabs/background.ts index 3e757cf2..5b2dba00 100644 --- a/src/components/sidebarLeft/tabs/background.ts +++ b/src/components/sidebarLeft/tabs/background.ts @@ -20,16 +20,16 @@ import { wrapPhoto } from "../../wrappers"; export default class AppBackgroundTab extends SliderSuperTab { init() { this.container.classList.add('background-container'); - this.title.innerText = 'Chat Background'; + this.setTitle('ChatBackground'); { const container = generateSection(this.scrollable); - const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'Upload Wallpaper', disabled: true}); - const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'Set a Color', disabled: true}); + const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'ChatBackground.UploadWallpaper', disabled: true}); + const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'ChatBackground.SetColor', disabled: true}); const blurCheckboxField = new CheckboxField({ - text: 'Blur Wallpaper Image', + text: 'ChatBackground.Blur', name: 'blur', stateKey: 'settings.background.blur', withRipple: true diff --git a/src/components/sidebarLeft/tabs/chatFolders.ts b/src/components/sidebarLeft/tabs/chatFolders.ts index 3e857364..005da4c4 100644 --- a/src/components/sidebarLeft/tabs/chatFolders.ts +++ b/src/components/sidebarLeft/tabs/chatFolders.ts @@ -2,7 +2,6 @@ import { SliderSuperTab } from "../../slider"; import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader"; import { RichTextProcessor } from "../../../lib/richtextprocessor"; import { attachClickEvent, cancelEvent, positionElementByIndex } from "../../../helpers/dom"; -import { ripple } from "../../ripple"; import { toast } from "../../toast"; import type { MyDialogFilter } from "../../../lib/storages/filters"; import type { DialogFilterSuggested, DialogFilter } from "../../../layer"; @@ -16,7 +15,7 @@ import rootScope from "../../../lib/rootScope"; import AppEditFolderTab from "./editFolder"; import Row from "../../row"; import { SettingSection } from ".."; -import { i18n_ } from "../../../lib/langPack"; +import { i18n, i18n_, LangPackKey } from "../../../lib/langPack"; export default class AppChatFoldersTab extends SliderSuperTab { private createFolderBtn: HTMLElement; @@ -30,13 +29,12 @@ export default class AppChatFoldersTab extends SliderSuperTab { private renderFolder(dialogFilter: DialogFilterSuggested | DialogFilter | MyDialogFilter, container?: HTMLElement, div?: HTMLElement) { let filter: DialogFilter | MyDialogFilter; let description = ''; - let d: string[] = []; + let d: HTMLElement[] = []; if(dialogFilter._ === 'dialogFilterSuggested') { filter = dialogFilter.filter; description = dialogFilter.description; } else { filter = dialogFilter; - description = ''; let enabledFilters = Object.keys(filter.pFlags).length; /* (['include_peers', 'exclude_peers'] as ['include_peers', 'exclude_peers']).forEach(key => { @@ -44,18 +42,17 @@ export default class AppChatFoldersTab extends SliderSuperTab { }); */ if(enabledFilters === 1) { - description = 'All '; - const pFlags = filter.pFlags; - if(pFlags.contacts) description += 'Contacts'; - else if(pFlags.non_contacts) description += 'Non-Contacts'; - else if(pFlags.groups) description += 'Groups'; - else if(pFlags.broadcasts) description += 'Channels'; - else if(pFlags.bots) description += 'Bots'; - else if(pFlags.exclude_muted) description += 'Unmuted'; - else if(pFlags.exclude_read) description += 'Unread'; - else if(pFlags.exclude_archived) description += 'Unarchived'; - d.push(description); + let k: LangPackKey; + if(pFlags.contacts) k = 'FilterAllContacts'; + else if(pFlags.non_contacts) k = 'FilterAllNonContacts'; + else if(pFlags.groups) k = 'FilterAllGroups'; + else if(pFlags.broadcasts) k = 'FilterAllChannels'; + else if(pFlags.bots) k = 'FilterAllBots'; + else if(pFlags.exclude_muted) k = 'FilterAllUnmuted'; + else if(pFlags.exclude_read) k = 'FilterAllUnread'; + else if(pFlags.exclude_archived) k = 'FilterAllUnarchived'; + d.push(i18n(k)); } else { const folder = appMessagesManager.dialogsStorage.getFolder(filter.id); let chats = 0, channels = 0, groups = 0; @@ -65,18 +62,32 @@ export default class AppChatFoldersTab extends SliderSuperTab { else chats++; } - if(chats) d.push(chats + ' chats'); - if(channels) d.push(channels + ' channels'); - if(groups) d.push(groups + ' groups'); + if(chats) d.push(i18n('Chats', [chats])); + if(channels) d.push(i18n('Channels', [channels])); + if(groups) d.push(i18n('Groups', [groups])); } } if(!div) { const row = new Row({ title: RichTextProcessor.wrapEmojiText(filter.title), - subtitle: d.length ? d.join(', ') : description, + subtitle: description, clickable: true }); + + if(d.length) { + let arr: HTMLElement[] = d.slice(0, 1); + for(let i = 1; i < d.length; ++i) { + const isLast = (d.length - 1) === i; + const delimiterKey: LangPackKey = isLast ? 'WordDelimiterLast' : 'WordDelimiter'; + arr.push(i18n(delimiterKey)); + arr.push(d[i]); + } + + arr.forEach(el => { + row.subtitle.append(el); + }); + } div = row.container; @@ -119,12 +130,12 @@ export default class AppChatFoldersTab extends SliderSuperTab { }); this.foldersSection = new SettingSection({ - name: 'ChatList.Filter.List.Header' + name: 'Filters' }); this.foldersSection.container.style.display = 'none'; this.suggestedSection = new SettingSection({ - name: 'ChatList.Filter.Recommended.Header' + name: 'FilterRecommended' }); this.suggestedSection.container.style.display = 'none'; @@ -208,7 +219,7 @@ export default class AppChatFoldersTab extends SliderSuperTab { suggestedFilters.forEach(filter => { const div = this.renderFolder(filter); - const button = Button('btn-primary btn-color-primary', {text: 'ChatList.Filter.Recommended.Add'}); + const button = Button('btn-primary btn-color-primary', {text: 'Add'}); div.append(button); this.suggestedSection.content.append(div); diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts index 2115c7c0..c5228ff1 100644 --- a/src/components/sidebarLeft/tabs/editFolder.ts +++ b/src/components/sidebarLeft/tabs/editFolder.ts @@ -39,15 +39,14 @@ export default class AppEditFolderTab extends SliderSuperTab { this.container.classList.add('edit-folder-container'); this.caption = document.createElement('div'); this.caption.classList.add('caption'); - this.caption.append(i18n(`Choose chats and types of chats that will -appear and never appear in this folder.`)); + this.caption.append(i18n('FilterIncludeExcludeInfo')); this.stickerContainer = document.createElement('div'); this.stickerContainer.classList.add('sticker-container'); this.confirmBtn = ButtonIcon('check btn-confirm hide blue'); const deleteFolderButton: ButtonMenuItemOptions = { icon: 'delete danger', - text: 'Delete Folder', + text: 'FilterMenuDelete', onClick: () => { deleteFolderButton.element.setAttribute('disabled', 'true'); appMessagesManager.filtersStorage.updateDialogFilter(this.filter, true).then(bool => { @@ -68,13 +67,13 @@ appear and never appear in this folder.`)); inputWrapper.classList.add('input-wrapper'); this.nameInputField = new InputField({ - label: 'Folder Name', + label: 'FilterNameInputLabel', maxLength: MAX_FOLDER_NAME_LENGTH }); inputWrapper.append(this.nameInputField.container); - const generateList = (className: string, h2Text: LangPackKey, buttons: {icon: string, name?: string, withRipple?: true, text: string}[], to: any) => { + const generateList = (className: string, h2Text: LangPackKey, buttons: {icon: string, name?: string, withRipple?: true, text: LangPackKey}[], to: any) => { const container = document.createElement('div'); container.classList.add('folder-list', className); @@ -104,7 +103,7 @@ appear and never appear in this folder.`)); return container; }; - this.include_peers = generateList('folder-list-included', 'ChatList.Filter.Include.Header', [{ + this.include_peers = generateList('folder-list-included', 'FilterInclude', [{ icon: 'add primary', text: 'ChatList.Filter.Include.AddChat', withRipple: true @@ -130,7 +129,7 @@ appear and never appear in this folder.`)); name: 'bots' }], this.flags); - this.exclude_peers = generateList('folder-list-excluded', 'ChatList.Filter.Exclude.Header', [{ + this.exclude_peers = generateList('folder-list-excluded', 'FilterExclude', [{ icon: 'minus primary', text: 'ChatList.Filter.Exclude.AddChat', withRipple: true @@ -227,7 +226,7 @@ appear and never appear in this folder.`)); private onCreateOpen() { this.caption.style.display = ''; - this.setTitle('New Folder'); + this.setTitle('FilterNew'); this.menuBtn.classList.add('hide'); this.confirmBtn.classList.remove('hide'); this.nameInputField.value = ''; @@ -240,7 +239,7 @@ appear and never appear in this folder.`)); private onEditOpen() { this.caption.style.display = 'none'; - this.setTitle(this.type === 'create' ? 'New Folder' : 'Edit Folder'); + this.setTitle(this.type === 'create' ? 'FilterNew' : 'FilterHeaderEdit'); if(this.type === 'edit') { this.menuBtn.classList.remove('hide'); @@ -344,4 +343,4 @@ appear and never appear in this folder.`)); return ret; } -} \ No newline at end of file +} diff --git a/src/components/sidebarLeft/tabs/editProfile.ts b/src/components/sidebarLeft/tabs/editProfile.ts index 02633838..e89a0301 100644 --- a/src/components/sidebarLeft/tabs/editProfile.ts +++ b/src/components/sidebarLeft/tabs/editProfile.ts @@ -31,7 +31,7 @@ export default class AppEditProfileTab extends SliderSuperTab { inputWrapper.classList.add('input-wrapper'); this.firstNameInputField = new InputField({ - label: 'Login.Register.FirstName.Placeholder', + label: 'EditProfile.FirstNameLabel', name: 'first-name', maxLength: 70 }); @@ -41,7 +41,7 @@ export default class AppEditProfileTab extends SliderSuperTab { maxLength: 64 }); this.bioInputField = new InputField({ - label: 'AccountSettings.Bio', + label: 'EditProfile.BioLabel', name: 'bio', maxLength: 70 }); @@ -76,7 +76,7 @@ export default class AppEditProfileTab extends SliderSuperTab { this.usernameInputField = new UsernameInputField({ peerId: 0, - label: 'EditAccount.Username', + label: 'EditProfile.Username.Label', name: 'username', plainText: true, listenerSetter: this.listenerSetter, @@ -84,21 +84,21 @@ export default class AppEditProfileTab extends SliderSuperTab { this.editPeer.handleChange(); this.setProfileUrl(); }, - availableText: 'Username is available', - takenText: 'Username is already taken', - invalidText: 'Username is invalid' + availableText: 'EditProfile.Username.Available', + takenText: 'EditProfile.Username.Taken', + invalidText: 'EditProfile.Username.Invalid' }); inputWrapper.append(this.usernameInputField.container); const caption = document.createElement('div'); caption.classList.add('caption'); - caption.append(i18n('UsernameSettings.ChangeDescription')); + caption.append(i18n('EditProfile.Username.Help')); caption.append(document.createElement('br'), document.createElement('br')); const profileUrlContainer = this.profileUrlContainer = document.createElement('div'); profileUrlContainer.classList.add('profile-url-container'); - profileUrlContainer.append(i18n('This link opens a chat with you:')); + profileUrlContainer.append(i18n('UsernameHelpLink', [''])); const profileUrlAnchor = this.profileUrlAnchor = document.createElement('a'); profileUrlAnchor.classList.add('profile-url'); diff --git a/src/components/sidebarLeft/tabs/generalSettings.ts b/src/components/sidebarLeft/tabs/generalSettings.ts index af03a68d..c4891dba 100644 --- a/src/components/sidebarLeft/tabs/generalSettings.ts +++ b/src/components/sidebarLeft/tabs/generalSettings.ts @@ -10,6 +10,7 @@ import { isApple } from "../../../helpers/userAgent"; import Row from "../../row"; import { attachClickEvent } from "../../../helpers/dom"; import AppBackgroundTab from "./background"; +import { LangPackKey, _i18n } from "../../../lib/langPack"; export class RangeSettingSelector { public container: HTMLDivElement; @@ -17,7 +18,7 @@ export class RangeSettingSelector { public onChange: (value: number) => void; - constructor(name: string, step: number, initialValue: number, minValue: number, maxValue: number) { + constructor(name: LangPackKey, step: number, initialValue: number, minValue: number, maxValue: number) { const BASE_CLASS = 'range-setting-selector'; this.container = document.createElement('div'); this.container.classList.add(BASE_CLASS); @@ -27,7 +28,7 @@ export class RangeSettingSelector { const nameDiv = document.createElement('div'); nameDiv.classList.add(BASE_CLASS + '-name'); - nameDiv.innerHTML = name; + _i18n(nameDiv, name); const valueDiv = document.createElement('div'); valueDiv.classList.add(BASE_CLASS + '-value'); @@ -55,26 +56,26 @@ export class RangeSettingSelector { export default class AppGeneralSettingsTab extends SliderSuperTab { init() { this.container.classList.add('general-settings-container'); - this.title.innerText = 'General'; + this.setTitle('General'); const section = generateSection.bind(null, this.scrollable); { const container = section('Settings'); - const range = new RangeSettingSelector('Message Text Size', 1, rootScope.settings.messagesTextSize, 12, 20); + const range = new RangeSettingSelector('TextSize', 1, rootScope.settings.messagesTextSize, 12, 20); range.onChange = (value) => { appStateManager.setByKey('settings.messagesTextSize', value); }; - const chatBackgroundButton = Button('btn-primary btn-transparent', {icon: 'photo', text: 'Chat Background'}); + const chatBackgroundButton = Button('btn-primary btn-transparent', {icon: 'photo', text: 'ChatBackground'}); attachClickEvent(chatBackgroundButton, () => { new AppBackgroundTab(this.slider).open(); }); const animationsCheckboxField = new CheckboxField({ - text: 'Enable Animations', + text: 'EnableAnimations', name: 'animations', stateKey: 'settings.animationsEnabled', withRipple: true @@ -84,58 +85,58 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { } { - const container = section('Keyboard'); + const container = section('General.Keyboard'); const form = document.createElement('form'); const enterRow = new Row({ radioField: new RadioField({ - text: 'Send by Enter', + text: 'General.SendShortcut.Enter', name: 'send-shortcut', value: 'enter', stateKey: 'settings.sendShortcut' }), - subtitle: 'New line by Shift + Enter', + subtitleLangKey: 'General.SendShortcut.NewLine.ShiftEnter' }); const ctrlEnterRow = new Row({ radioField: new RadioField({ - text: `Send by ${isApple ? '⌘' : 'Ctrl'} + Enter`, name: 'send-shortcut', value: 'ctrlEnter', stateKey: 'settings.sendShortcut' }), - subtitle: 'New line by Enter', + subtitleLangKey: 'General.SendShortcut.NewLine.Enter' }); + _i18n(ctrlEnterRow.radioField.main, 'General.SendShortcut.CtrlEnter', [isApple ? '⌘' : 'Ctrl']); form.append(enterRow.container, ctrlEnterRow.container); container.append(form); } { - const container = section('Auto-Download Media'); + const container = section('AutoDownloadMedia'); //container.classList.add('sidebar-left-section-disabled'); const contactsCheckboxField = new CheckboxField({ - text: 'Contacts', + text: 'AutodownloadContacts', name: 'contacts', stateKey: 'settings.autoDownload.contacts', withRipple: true }); const privateCheckboxField = new CheckboxField({ - text: 'Private Chats', + text: 'AutodownloadPrivateChats', name: 'private', stateKey: 'settings.autoDownload.private', withRipple: true }); const groupsCheckboxField = new CheckboxField({ - text: 'Group Chats', + text: 'AutodownloadGroupChats', name: 'groups', stateKey: 'settings.autoDownload.groups', withRipple: true }); const channelsCheckboxField = new CheckboxField({ - text: 'Channels', + text: 'AutodownloadChannels', name: 'channels', stateKey: 'settings.autoDownload.channels', withRipple: true @@ -145,17 +146,17 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { } { - const container = section('Auto-Play Media'); + const container = section('General.AutoplayMedia'); //container.classList.add('sidebar-left-section-disabled'); const gifsCheckboxField = new CheckboxField({ - text: 'GIFs', + text: 'AutoplayGIF', name: 'gifs', stateKey: 'settings.autoPlay.gifs', withRipple: true }); const videosCheckboxField = new CheckboxField({ - text: 'Videos', + text: 'AutoplayVideo', name: 'videos', stateKey: 'settings.autoPlay.videos', withRipple: true @@ -165,16 +166,16 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { } { - const container = section('Stickers'); + const container = section('Telegram.InstalledStickerPacksController'); const suggestCheckboxField = new CheckboxField({ - text: 'Suggest Stickers by Emoji', + text: 'Stickers.SuggestStickers', name: 'suggest', stateKey: 'settings.stickers.suggest', withRipple: true }); const loopCheckboxField = new CheckboxField({ - text: 'Loop Animated Stickers', + text: 'InstalledStickers.LoopAnimated', name: 'loop', stateKey: 'settings.stickers.loop', withRipple: true @@ -190,4 +191,4 @@ export default class AppGeneralSettingsTab extends SliderSuperTab { this.init = null; } } -} \ No newline at end of file +} diff --git a/src/components/sidebarLeft/tabs/includedChats.ts b/src/components/sidebarLeft/tabs/includedChats.ts index 6f82cfeb..7b9d5706 100644 --- a/src/components/sidebarLeft/tabs/includedChats.ts +++ b/src/components/sidebarLeft/tabs/includedChats.ts @@ -124,7 +124,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab { dom.containerEl.append(this.checkbox(selected)); if(selected) dom.listEl.classList.add('active'); - let subtitle: LangPackKey; + /* let subtitle: LangPackKey; if(peerId > 0) { if(peerId === rootScope.myId) { @@ -138,7 +138,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab { subtitle = appPeersManager.isBroadcast(peerId) ? 'Channel' : 'Group'; } - _i18n(dom.lastMessageSpan, subtitle); + _i18n(dom.lastMessageSpan, subtitle); */ }); }; @@ -149,19 +149,19 @@ export default class AppIncludedChatsTab extends SliderSuperTab { } this.confirmBtn.style.display = this.type === 'excluded' ? '' : 'none'; - this.setTitle(this.type === 'included' ? 'Included Chats' : 'Excluded Chats'); + this.setTitle(this.type === 'included' ? 'FilterAlwaysShow' : 'FilterNeverShow'); const filter = this.filter; const fragment = document.createDocumentFragment(); const dd = document.createElement('div'); dd.classList.add('sidebar-left-h2'); - _i18n(dd, 'ChatList.Add.TopSeparator'); + _i18n(dd, 'FilterChatTypes'); const categories = document.createElement('div'); categories.classList.add('folder-categories'); - let details: {[flag: string]: {ico: string, text: string}}; + let details: {[flag: string]: {ico: string, text: LangPackKey}}; if(this.type === 'excluded') { details = { exclude_muted: {ico: 'mute', text: 'ChatList.Filter.MutedChats'}, @@ -192,7 +192,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab { const d = document.createElement('div'); d.classList.add('sidebar-left-h2'); - _i18n(d, 'ChatList.Add.BottomSeparator'); + _i18n(d, 'FilterChats'); fragment.append(dd, categories, hr, d); diff --git a/src/components/sidebarLeft/tabs/notifications.ts b/src/components/sidebarLeft/tabs/notifications.ts index 48da17e5..c8c5bbfa 100644 --- a/src/components/sidebarLeft/tabs/notifications.ts +++ b/src/components/sidebarLeft/tabs/notifications.ts @@ -18,7 +18,7 @@ export default class AppNotificationsTab extends SliderSuperTabEventable { const NotifySection = (options: { name: LangPackKey, - typeText: string, + typeText: LangPackKey, inputKey: InputNotifyKey, }) => { const section = new SettingSection({ @@ -27,12 +27,12 @@ export default class AppNotificationsTab extends SliderSuperTabEventable { const enabledRow = new Row({ checkboxField: new CheckboxField({text: options.typeText, checked: true}), - subtitle: 'Loading...', + subtitleLangKey: 'Loading', }); const previewEnabledRow = new Row({ - checkboxField: new CheckboxField({text: 'Message preview', checked: true}), - subtitle: 'Loading...', + checkboxField: new CheckboxField({text: 'Notifications.MessagePreview', checked: true}), + subtitleLangKey: 'Loading', }); section.content.append(enabledRow.container, previewEnabledRow.container); @@ -80,35 +80,35 @@ export default class AppNotificationsTab extends SliderSuperTabEventable { NotifySection({ name: 'AutoDownloadSettings.TypePrivateChats', - typeText: 'Notifications for private chats', + typeText: 'NotificationsForPrivateChats', inputKey: 'inputNotifyUsers' }); NotifySection({ - name: 'DataAndStorage.CategorySettings.GroupChats', - typeText: 'Notifications for groups', + name: 'AutoDownloadSettings.TypeGroupChats', + typeText: 'NotificationsForGroups', inputKey: 'inputNotifyChats' }); NotifySection({ name: 'AutoDownloadSettings.TypeChannels', - typeText: 'Notifications for channels', + typeText: 'NotificationsForChannels', inputKey: 'inputNotifyBroadcasts' }); { const section = new SettingSection({ - name: 'Suggest.Localization.Other' + name: 'NotificationsOther' }); const contactsSignUpRow = new Row({ - checkboxField: new CheckboxField({text: 'Contacts joined Telegram', checked: true}), - subtitle: 'Loading...', + checkboxField: new CheckboxField({text: 'ContactJoined', checked: true}), + subtitleLangKey: 'Loading', }); const soundRow = new Row({ - checkboxField: new CheckboxField({text: 'Notification sound', checked: true, stateKey: 'settings.notifications.sound'}), - subtitle: 'Enabled', + checkboxField: new CheckboxField({text: 'Notifications.Sound', checked: true, stateKey: 'settings.notifications.sound'}), + subtitleLangKey: 'Checkbox.Enabled', }); section.content.append(contactsSignUpRow.container, soundRow.container); diff --git a/src/components/sidebarLeft/tabs/privacyAndSecurity.ts b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts index a9d4c8ec..0ebedc14 100644 --- a/src/components/sidebarLeft/tabs/privacyAndSecurity.ts +++ b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts @@ -19,6 +19,7 @@ import AppBlockedUsersTab from "./blockedUsers"; import appUsersManager from "../../../lib/appManagers/appUsersManager"; import rootScope from "../../../lib/rootScope"; import { convertKeyToInputKey } from "../../../helpers/string"; +import { LangPackKey, _i18n } from "../../../lib/langPack"; export default class AppPrivacyAndSecurityTab extends SliderSuperTab { private activeSessionsRow: Row; @@ -26,11 +27,11 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { protected init() { this.container.classList.add('privacy-container'); - this.title.innerText = 'Privacy and Security'; + this.setTitle('PrivacySettings'); const section = generateSection.bind(null, this.scrollable); - const SUBTITLE = 'Loading...'; + const SUBTITLE: LangPackKey = 'Loading'; { const section = new SettingSection({noDelimiter: true}); @@ -38,8 +39,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { let blockedPeerIds: number[]; const blockedUsersRow = new Row({ icon: 'deleteuser', - title: 'Blocked Users', - subtitle: SUBTITLE, + titleLangKey: 'BlockedUsers', + subtitleLangKey: SUBTITLE, clickable: () => { const tab = new AppBlockedUsersTab(this.slider); tab.peerIds = blockedPeerIds; @@ -51,8 +52,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { let passwordState: AccountPassword; const twoFactorRowOptions = { icon: 'lock', - title: 'Two-Step Verification', - subtitle: SUBTITLE, + titleLangKey: 'TwoStepVerification' as LangPackKey, + subtitleLangKey: SUBTITLE, clickable: (e: Event) => { let tab: AppTwoStepVerificationTab | AppTwoStepVerificationEnterPasswordTab | AppTwoStepVerificationEmailConfirmationTab; if(passwordState.pFlags.has_password) { @@ -77,8 +78,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const activeSessionsRow = this.activeSessionsRow = new Row({ icon: 'activesessions', - title: 'Active Sessions', - subtitle: SUBTITLE, + titleLangKey: 'SessionsTitle', + subtitleLangKey: SUBTITLE, clickable: () => { const tab = new AppActiveSessionsTab(this.slider); tab.privacyTab = this; @@ -94,7 +95,12 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { let blockedCount: number; const setBlockedCount = (count: number) => { blockedCount = count; - blockedUsersRow.subtitle.innerText = count ? (count + ' ' + (count > 1 ? 'users' : 'user')) : 'None'; + + if(count) { + _i18n(blockedUsersRow.subtitle, 'Privacy.BlockedUsers', [count]); + } else { + _i18n(blockedUsersRow.subtitle, 'Privacy.BlockedUsers.None'); + } }; this.listenerSetter.add(rootScope, 'peer_block', () => { @@ -128,7 +134,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { } { - const container = section('Privacy'); + const container = section('PrivacyTitle'); container.classList.add('privacy-navigation-container'); @@ -137,48 +143,48 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { }> = {}; const numberVisibilityRow = rowsByKeys['inputPrivacyKeyPhoneNumber'] = new Row({ - title: 'Who can see my phone number?', - subtitle: SUBTITLE, + titleLangKey: 'PrivacyPhoneTitle', + subtitleLangKey: SUBTITLE, clickable: () => { new AppPrivacyPhoneNumberTab(this.slider).open() } }); const lastSeenTimeRow = rowsByKeys['inputPrivacyKeyStatusTimestamp'] = new Row({ - title: 'Who can see your Last Seen time?', - subtitle: SUBTITLE, + titleLangKey: 'LastSeenTitle', + subtitleLangKey: SUBTITLE, clickable: () => { new AppPrivacyLastSeenTab(this.slider).open() } }); const photoVisibilityRow = rowsByKeys['inputPrivacyKeyProfilePhoto'] = new Row({ - title: 'Who can see my profile photo?', - subtitle: SUBTITLE, + titleLangKey: 'PrivacyProfilePhotoTitle', + subtitleLangKey: SUBTITLE, clickable: () => { new AppPrivacyProfilePhotoTab(this.slider).open(); } }); const callRow = rowsByKeys['inputPrivacyKeyPhoneCall'] = new Row({ - title: 'Who can call me?', - subtitle: SUBTITLE, + titleLangKey: 'WhoCanCallMe', + subtitleLangKey: SUBTITLE, clickable: () => { new AppPrivacyCallsTab(this.slider).open(); } }); const linkAccountRow = rowsByKeys['inputPrivacyKeyForwards'] = new Row({ - title: 'Who can add a link to my account when forwarding my messages?', - subtitle: SUBTITLE, + titleLangKey: 'PrivacyForwardsTitle', + subtitleLangKey: SUBTITLE, clickable: () => { new AppPrivacyForwardMessagesTab(this.slider).open(); } }); const groupChatsAddRow = rowsByKeys['inputPrivacyKeyChatInvite'] = new Row({ - title: 'Who can add me to group chats?', - subtitle: SUBTITLE, + titleLangKey: 'WhoCanAddMe', + subtitleLangKey: SUBTITLE, clickable: () => { new AppPrivacyAddToGroupsTab(this.slider).open(); } @@ -216,7 +222,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { apiManager.invokeApi('account.getAuthorizations').then(auths => { this.activeSessionsRow.freezed = false; this.authorizations = auths.authorizations; - this.activeSessionsRow.subtitle.innerText = this.authorizations.length + ' ' + (this.authorizations.length !== 1 ? 'devices' : 'device'); + _i18n(this.activeSessionsRow.subtitle, 'Privacy.Devices', [this.authorizations.length]); //console.log('auths', auths); }); } diff --git a/src/lang.ts b/src/lang.ts new file mode 100644 index 00000000..9047323d --- /dev/null +++ b/src/lang.ts @@ -0,0 +1,140 @@ +const lang = { + FilterIncludeExcludeInfo: 'Choose chats and types of chats that will\nappear and never appear in this folder.', + FilterNameInputLabel: 'Folder Name', + FilterMenuDelete: 'Delete Folder', + FilterHeaderEdit: 'Edit Folder', + FilterAllGroups: 'All Groups', + FilterAllContacts: 'All Contacts', + FilterAllNonContacts: 'All Non-Contacts', + FilterAllChannels: 'All Channels', + FilterAllBots: 'All Bots', + FilterAllUnmuted: 'All Unmuted', + FilterAllUnread: 'All Unread', + FilterAllUnarchived: 'All Unarchived', + WordDelimiter: ', ', + WordDelimiterLast: ' and ', + "EditProfile.FirstNameLabel": 'Name', + "EditProfile.BioLabel": 'Bio (optional)', + "EditProfile.Username.Label": 'Username (optional)', + "EditProfile.Username.Available": 'Username is available', + "EditProfile.Username.Taken": 'Username is already taken', + "EditProfile.Username.Invalid": 'Username is invalid', + "EditProfile.Username.Help": "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\nYou can use a–z, 0–9 and underscores. Minimum length is 5 characters.", + "ChatList.Menu.Archived": "Archived", + Saved: "Saved", + "General.Keyboard": "Keyboard", + "General.SendShortcut.Enter": "Send by Enter", + "General.SendShortcut.CtrlEnter": "Send by %s + Enter", + "General.SendShortcut.NewLine.ShiftEnter": "New line by Shift + Enter", + "General.SendShortcut.NewLine.Enter": "New line by Enter", + "General.AutoplayMedia": "Auto-Play Media", + "ChatBackground.UploadWallpaper": "Upload Wallpaper", + "ChatBackground.SetColor": "Upload Wallpaper", + "ChatBackground.Blur": "Upload Wallpaper", + "Notifications.Sound": "Notification Sound", + "Notifications.MessagePreview": "Message preview", + "Checkbox.Enabled": "Enabled", + "Checkbox.Disabled": "Disabled", + "Privacy.Devices": { + one_value: '%1$d device', + other_value: '%1$d devices' + }, + "Privacy.BlockedUsers": { + one_value: '%1$d user', + other_value: '%1$d users', + }, + "Privacy.BlockedUsers.None": 'None', + + // * android + FilterAlwaysShow: 'Include Chats', + FilterNeverShow: 'Exclude Chats', + FilterInclude: 'Included Chats', + FilterExclude: 'Excluded Chats', + FilterChatTypes: 'Chat types', + FilterChats: 'Chats', + FilterNew: 'New Folder', + Filters: 'Folders', + FilterRecommended: 'Recommended Folders', + Add: 'Add', + Chats: { + one_value: '%1$d chat', + other_value: '%1$d chats' + }, + Channels: { + one_value: '%1$d channel', + other_value: '%1$d channels' + }, + Groups: { + one_value: '%1$d group', + other_value: '%1$d groups' + }, + UsernameHelpLink: "This link opens a chat with you:\n%1$s", + NewGroup: "New Group", + Contacts: "Contacts", + SavedMessages: "Saved Messages", + Settings: "Settings", + SettingsHelp: "Help", + General: "General", + TextSize: "Message Text Size", + ChatBackground: "Chat Background", + EnableAnimations: "Enable Animations", + AutoDownloadMedia: "Auto-Download Media", + AutodownloadContacts: 'Contacts', + AutodownloadPrivateChats: 'Private Chats', + AutodownloadGroupChats: 'Group Chats', + AutodownloadChannels: 'Channels', + AutoplayGIF: 'GIFs', + AutoplayVideo: 'Videos', + NotificationsForGroups: 'Notifications for groups', + NotificationsForPrivateChats: 'Notifications for private chats', + NotificationsForChannels: 'Notifications for channels', + NotificationsOther: 'Other', + ContactJoined: 'Contact joined Telegram', + Loading: "Loading...", + BlockedUsers: "Blocked Users", + TwoStepVerification: "Two-Step Verification", + PrivacySettings: "Privacy and Security", + PrivacyTitle: "Privacy", + PrivacyPhoneTitle: "Who can see my phone number?", + PrivacyProfilePhotoTitle: "Who can see my profile photos & videos?", + PrivacyForwardsTitle: "Who can add a link to my account when forwarding my messages?", + LastSeenTitle: "Who can see your Last Seen time?", + SessionsTitle: "Active Sessions", + WhoCanCallMe: "Who can call me?", + WhoCanAddMe: "Who can add me to group chats?", + + // * macos + "ChatList.Filter.Header": "Create folders for different groups of chats and quickly switch between them.", + "ChatList.Filter.NewTitle": "Create Folder", + "ChatList.Filter.List.Title": "Chat Folders", + "ChatList.Filter.Include.AddChat": "Add Chats", + "ChatList.Filter.Exclude.AddChat": "Add Chats", + "ChatList.Filter.All": "All", + "ChatList.Filter.Contacts": "Contacts", + "ChatList.Filter.NonContacts": "Non-Contacts", + "ChatList.Filter.Groups": "Groups", + "ChatList.Filter.Channels": "Channels", + "ChatList.Filter.Bots": "Bots", + "ChatList.Filter.MutedChats": "Muted", + "ChatList.Filter.ReadChats": "Read", + "ChatList.Filter.Archive": "Archived", + "Bio.Description": "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco", + "EditAccount.Username": "Username", + "EditAccount.Title": "Edit Profile", + "EditAccount.Logout": "Log Out", + "Login.Register.LastName.Placeholder": "Last Name", + "AccountSettings.Filters": "Chat Folders", + "AccountSettings.Notifications": "Notifications and Sounds", + "AccountSettings.PrivacyAndSecurity": "Privacy and Security", + "AccountSettings.Language": "Language", + "Telegram.GeneralSettingsViewController": "General Settings", + "Telegram.InstalledStickerPacksController": "Stickers", + "Telegram.NotificationSettingsViewController": "Notifications", + "Stickers.SuggestStickers": "Suggest Stickers by Emoji", + "InstalledStickers.LoopAnimated": "Loop Animated Stickers", + "AutoDownloadSettings.TypePrivateChats": "Private Chats", + "AutoDownloadSettings.TypeGroupChats": "Groups", + "AutoDownloadSettings.TypeChannels": "Channels", +}; + +export default lang; diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 779e2d3a..945422d3 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -318,25 +318,7 @@ export class AppImManager { const chat = this.chat; - if(e.key === 'Escape') { - let cancel = true; - if(this.markupTooltip?.container?.classList.contains('is-visible')) { - this.markupTooltip.hide(); - } else if(chat.selection.isSelecting) { - chat.selection.cancelSelection(); - } else if(chat.container.classList.contains('is-helper-active')) { - chat.input.replyElements.cancelBtn.click(); - } else if(chat.peerId !== 0) { // hide current dialog - this.setPeer(0); - } else { - cancel = false; - } - - // * cancel event for safari, because if application is in fullscreen, browser will try to exit fullscreen - if(cancel) { - cancelEvent(e); - } - } else if(e.key === 'Meta' || e.key === 'Control') { + if(e.key === 'Meta' || e.key === 'Control') { return; } else if(e.code === "KeyC" && (e.ctrlKey || e.metaKey) && target.tagName !== 'INPUT') { return; diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index c13acb82..d6422756 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -1,5 +1,6 @@ import { MOUNT_CLASS_TO } from "../config/debug"; import { safeAssign } from "../helpers/object"; +import type lang from "../lang"; import { LangPackDifference, LangPackString } from "../layer"; import apiManager from "./mtproto/mtprotoworker"; import sessionStorage from "./sessionStorage"; @@ -34,37 +35,7 @@ export const langPack: {[actionType: string]: string} = { "messageActionBotAllowed": "You allowed this bot to message you when logged in {}" }; -namespace Strings { - export type Bio = 'Bio.Description'; - - export type LoginRegister = 'Login.Register.FirstName.Placeholder' | 'Login.Register.LastName.Placeholder'; - - export type EditAccount = 'EditAccount.Logout' | 'EditAccount.Title' | 'EditAccount.Title' | 'EditAccount.Username'; - - export type AccountSettings = 'AccountSettings.Filters' | 'AccountSettings.Notifications' | 'AccountSettings.PrivacyAndSecurity' | 'AccountSettings.Language' | 'AccountSettings.Bio'; - - export type Telegram = 'Telegram.GeneralSettingsViewController' | 'Telegram.NotificationSettingsViewController' | 'Telegram.LanguageViewController'; - - export type ChatList = ChatListFilter; - export type ChatListAdd = 'ChatList.Add.TopSeparator' | 'ChatList.Add.BottomSeparator'; - export type ChatListFilterIncluded = 'ChatList.Filter.Include.Header' | 'ChatList.Filter.Include.AddChat'; - export type ChatListFilterExcluded = 'ChatList.Filter.Exclude.Header' | 'ChatList.Filter.Exclude.AddChat'; - export type ChatListFilterList = 'ChatList.Filter.List.Header' | 'ChatList.Filter.List.Title'; - export type ChatListFilterRecommended = 'ChatList.Filter.Recommended.Header' | 'ChatList.Filter.Recommended.Add'; - export type ChatListFilter = ChatListAdd | ChatListFilterIncluded | ChatListFilterExcluded | ChatListFilterList | ChatListFilterRecommended | 'ChatList.Filter.Header' | 'ChatList.Filter.NewTitle' | 'ChatList.Filter.NonContacts' | 'ChatList.Filter.Contacts' | 'ChatList.Filter.Groups' | 'ChatList.Filter.Channels' | 'ChatList.Filter.Bots'; - - export type AutoDownloadSettings = 'AutoDownloadSettings.TypePrivateChats' | 'AutoDownloadSettings.TypeChannels'; - - export type DataAndStorage = 'DataAndStorage.CategorySettings.GroupChats'; - - export type Suggest = 'Suggest.Localization.Other'; - - export type UsernameSettings = 'UsernameSettings.ChangeDescription'; - - export type LangPackKey = string | AccountSettings | EditAccount | Telegram | ChatList | LoginRegister | Bio | AutoDownloadSettings | DataAndStorage | Suggest | UsernameSettings; -} - -export type LangPackKey = Strings.LangPackKey; +export type LangPackKey = string | keyof typeof lang; namespace I18n { export const strings: Map = new Map(); @@ -76,7 +47,7 @@ namespace I18n { sessionStorage.get('langPack'), polyfillPromise ]).then(([langPack]) => { - if(!langPack) { + if(!langPack || true) { return getLangPack('en'); } @@ -96,8 +67,40 @@ namespace I18n { lang_code: langCode, lang_pack: 'macos' }), + apiManager.invokeApi('langpack.getLangPack', { + lang_code: langCode, + lang_pack: 'android' + }), + import('../lang'), polyfillPromise - ]).then(([langPack, _]) => { + ]).then(([langPack, _langPack, __langPack, _]) => { + let strings: LangPackString[] = []; + for(const i in __langPack.default) { + // @ts-ignore + const v = __langPack.default[i]; + if(typeof(v) === 'string') { + strings.push({ + _: 'langPackString', + key: i, + value: v + }); + } else { + strings.push({ + _: 'langPackStringPluralized', + key: i, + ...v + }); + } + } + + strings = strings.concat(langPack.strings); + + for(const string of _langPack.strings) { + strings.push(string); + } + + langPack.strings = strings; + return sessionStorage.set({langPack}).then(() => { applyLangPack(langPack); return langPack; @@ -106,7 +109,7 @@ namespace I18n { } export const polyfillPromise = (function checkIfPolyfillNeeded() { - if(typeof(Intl) !== 'undefined' && typeof(Intl.PluralRules) !== 'undefined'/* && false */) { + if(typeof(Intl) !== 'undefined' && typeof(Intl.PluralRules) !== 'undefined' && false) { return Promise.resolve(); } else { return import('./pluralPolyfill').then(_Intl => { @@ -143,24 +146,28 @@ namespace I18n { let out = ''; if(str) { - if(str._ === 'langPackStringPluralized') { + if(str._ === 'langPackStringPluralized' && args?.length) { const v = args[0] as number; const s = pluralRules.select(v); // @ts-ignore - out = str[s + '_value']; + out = str[s + '_value'] || str['other_value']; } else if(str._ === 'langPackString') { out = str.value; } else { - //out = '[' + key + ']'; - out = key; + out = '[' + key + ']'; + //out = key; } } else { - //out = '[' + key + ']'; - out = key; + out = '[' + key + ']'; + //out = key; } + out = out + .replace(/\n/g, '
') + .replace(/\*\*(.+?)\*\*/g, '$1'); + if(args?.length) { - out = out.replace(/%./g, (match, offset, string) => { + out = out.replace(/%\d\$.|%./g, (match, offset, string) => { return '' + args.shift(); }); } @@ -172,7 +179,7 @@ namespace I18n { export type IntlElementOptions = { element?: HTMLElement, - property?: 'innerText' | 'innerHTML' | 'placeholder' + property?: /* 'innerText' | */'innerHTML' | 'placeholder' key: LangPackKey, args?: any[] }; @@ -180,7 +187,7 @@ namespace I18n { public element: IntlElementOptions['element']; public key: IntlElementOptions['key']; public args: IntlElementOptions['args']; - public property: IntlElementOptions['property'] = 'innerText'; + public property: IntlElementOptions['property'] = 'innerHTML'; constructor(options: IntlElementOptions) { this.element = options.element || document.createElement('span'); @@ -193,7 +200,8 @@ namespace I18n { public update(options?: IntlElementOptions) { safeAssign(this, options); - (this.element as any)[this.property] = getString(this.key, this.args); + const str = getString(this.key, this.args); + (this.element as any)[this.property] = str; } }