diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts
index 7b1a7efa..5377669f 100644
--- a/src/components/appSelectPeers.ts
+++ b/src/components/appSelectPeers.ts
@@ -10,6 +10,7 @@ import Scrollable from "./scrollable";
import { FocusDirection } from "../helpers/fastSmoothScroll";
import CheckboxField from "./checkboxField";
import appProfileManager from "../lib/appManagers/appProfileManager";
+import { safeAssign } from "../helpers/object";
type PeerType = 'contacts' | 'dialogs' | 'channelParticipants';
@@ -69,7 +70,7 @@ export default class AppSelectPeers {
rippleEnabled?: boolean,
avatarSize?: AppSelectPeers['avatarSize'],
}) {
- Object.assign(this, options);
+ safeAssign(this, options);
this.container.classList.add('selector');
@@ -440,7 +441,7 @@ export default class AppSelectPeers {
});
}
- public add(peerId: any, title?: string, scroll = true) {
+ public add(peerId: any, title?: string | HTMLElement, scroll = true) {
//console.trace('add');
this.selected.add(peerId);
@@ -467,7 +468,12 @@ export default class AppSelectPeers {
}
if(title) {
- div.innerHTML = title;
+ if(typeof(title) === 'string') {
+ div.innerHTML = title;
+ } else {
+ div.innerHTML = '';
+ div.append(title);
+ }
}
div.insertAdjacentElement('afterbegin', avatarEl);
diff --git a/src/components/editPeer.ts b/src/components/editPeer.ts
index 9d008f4c..882ff66e 100644
--- a/src/components/editPeer.ts
+++ b/src/components/editPeer.ts
@@ -4,6 +4,7 @@ import AvatarElement from "./avatar";
import InputField from "./inputField";
import ListenerSetter from "../helpers/listenerSetter";
import Button from "./button";
+import { safeAssign } from "../helpers/object";
export default class EditPeer {
public nextBtn: HTMLButtonElement;
@@ -23,7 +24,7 @@ export default class EditPeer {
listenerSetter: ListenerSetter,
doNotEditAvatar?: boolean,
}) {
- Object.assign(this, options);
+ safeAssign(this, options);
this.nextBtn = Button('btn-circle btn-corner tgico-check');
diff --git a/src/components/inputField.ts b/src/components/inputField.ts
index fceef259..dc454d37 100644
--- a/src/components/inputField.ts
+++ b/src/components/inputField.ts
@@ -1,7 +1,7 @@
import { getRichValue, isInputEmpty } from "../helpers/dom";
import { debounce } from "../helpers/schedulers";
import { checkRTL } from "../helpers/string";
-import { i18n_, LangPackKey } from "../lib/langPack";
+import { i18n, LangPackKey } from "../lib/langPack";
import RichTextProcessor from "../lib/richtextprocessor";
let init = () => {
@@ -143,7 +143,7 @@ class InputField {
if(label) {
this.label = document.createElement('label');
- i18n_({element: this.label, key: label});
+ this.label.append(i18n(label));
this.container.append(this.label);
}
@@ -234,9 +234,10 @@ class InputField {
}
}
- public setState(state: InputState, label?: string) {
+ public setState(state: InputState, label?: LangPackKey) {
if(label) {
- this.label.innerHTML = label;
+ this.label.innerHTML = '';
+ this.label.append(i18n(label));
}
this.input.classList.toggle('error', !!(state & InputState.Error));
diff --git a/src/components/preloader.ts b/src/components/preloader.ts
index f7a0a01e..b17fa185 100644
--- a/src/components/preloader.ts
+++ b/src/components/preloader.ts
@@ -2,6 +2,7 @@ import { isInDOM, cancelEvent, attachClickEvent } from "../helpers/dom";
import { CancellablePromise } from "../helpers/cancellablePromise";
import SetTransition from "./singleTransition";
import { fastRaf } from "../helpers/schedulers";
+import { safeAssign } from "../helpers/object";
const TRANSITION_TIME = 200;
@@ -34,7 +35,7 @@ export default class ProgressivePreloader {
attachMethod: ProgressivePreloader['attachMethod']
}>) {
if(options) {
- Object.assign(this, options);
+ safeAssign(this, options);
}
}
diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts
index 2df87c85..a7d79d75 100644
--- a/src/components/sidebarLeft/index.ts
+++ b/src/components/sidebarLeft/index.ts
@@ -24,6 +24,8 @@ 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';
@@ -34,14 +36,6 @@ export class AppSidebarLeft extends SidebarSlider {
private inputSearch: InputSearch;
private menuEl: HTMLElement;
- private buttons: {
- newGroup: HTMLButtonElement,
- contacts: HTMLButtonElement,
- archived: HTMLButtonElement,
- saved: HTMLButtonElement,
- settings: HTMLButtonElement,
- help: HTMLButtonElement
- } = {} as any;
public archivedCount: HTMLSpanElement;
private newBtnMenu: HTMLElement;
@@ -68,58 +62,87 @@ export class AppSidebarLeft extends SidebarSlider {
const sidebarHeader = this.sidebarEl.querySelector('.item-main .sidebar-header');
sidebarHeader.append(this.inputSearch.container);
+ const onNewGroupClick = () => {
+ new AppAddMembersTab(this).open({
+ peerId: 0,
+ type: 'chat',
+ skippable: false,
+ takeOut: (peerIds) => {
+ new AppNewGroupTab(this).open(peerIds);
+ },
+ title: 'Add Members',
+ placeholder: 'Add People...'
+ });
+ };
+
+ const onContactsClick = () => {
+ new AppContactsTab(this).open();
+ };
+
this.toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
this.backBtn = this.sidebarEl.querySelector('.sidebar-back-button') as HTMLButtonElement;
+ const btnArchive: ButtonMenuItemOptions = {
+ icon: 'archive',
+ text: 'Archived',
+ onClick: () => {
+ new AppArchivedTab(this).open();
+ }
+ };
+
+ const btnMenu = ButtonMenu([{
+ icon: 'newgroup',
+ text: 'New Group',
+ onClick: onNewGroupClick
+ }, {
+ icon: 'user',
+ text: 'Contacts',
+ onClick: onContactsClick
+ }, btnArchive, {
+ icon: 'savedmessages',
+ text: 'Saved',
+ onClick: () => {
+ setTimeout(() => { // menu doesn't close if no timeout (lol)
+ appImManager.setPeer(appImManager.myId);
+ }, 0);
+ }
+ }, {
+ icon: 'settings',
+ text: 'Settings',
+ onClick: () => {
+ new AppSettingsTab(this).open();
+ }
+ }, {
+ icon: 'help btn-disabled',
+ text: 'Help',
+ onClick: () => {
+
+ }
+ }]);
+
+ btnMenu.classList.add('bottom-right');
+
+ this.toolsBtn.append(btnMenu);
+
this.menuEl = this.toolsBtn.querySelector('.btn-menu');
this.newBtnMenu = this.sidebarEl.querySelector('#new-menu');
this.inputSearch.input.addEventListener('focus', () => this.initSearch(), {once: true});
- parseMenuButtonsTo(this.buttons, this.menuEl.children);
parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children);
- this.archivedCount = this.buttons.archived.querySelector('.archived-count') as HTMLSpanElement;
-
- attachClickEvent(this.buttons.saved, (e) => {
- ///////this.log('savedbtn click');
- setTimeout(() => { // menu doesn't close if no timeout (lol)
- appImManager.setPeer(appImManager.myId);
- }, 0);
- });
-
- attachClickEvent(this.buttons.archived, (e) => {
- new AppArchivedTab(this).open();
- });
+ this.archivedCount = document.createElement('span');
+ this.archivedCount.className = 'archived-count badge badge-24 badge-gray';
- [this.newButtons.privateChat, this.buttons.contacts].forEach(btn => {
- attachClickEvent(btn, (e) => {
- new AppContactsTab(this).open();
- });
- });
+ btnArchive.element.append(this.archivedCount);
- attachClickEvent(this.buttons.settings, (e) => {
- new AppSettingsTab(this).open();
- });
+ attachClickEvent(this.newButtons.privateChat, onContactsClick);
attachClickEvent(this.newButtons.channel, (e) => {
new AppNewChannelTab(this).open();
});
- [this.newButtons.group, this.buttons.newGroup].forEach(btn => {
- attachClickEvent(btn, (e) => {
- new AppAddMembersTab(this).open({
- peerId: 0,
- type: 'chat',
- skippable: false,
- takeOut: (peerIds) => {
- new AppNewGroupTab(this).open(peerIds);
- },
- title: 'Add Members',
- placeholder: 'Add People...'
- });
- });
- });
+ attachClickEvent(this.newButtons.group, onNewGroupClick);
rootScope.on('dialogs_archived_unread', (e) => {
this.archivedCount.innerText = '' + formatNumber(e.count, 1);
diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts
index 1ec37b4b..2115c7c0 100644
--- a/src/components/sidebarLeft/tabs/editFolder.ts
+++ b/src/components/sidebarLeft/tabs/editFolder.ts
@@ -13,6 +13,7 @@ import ButtonMenuToggle from "../../buttonMenuToggle";
import { ButtonMenuItemOptions } from "../../buttonMenu";
import Button from "../../button";
import AppIncludedChatsTab from "./includedChats";
+import { i18n, i18n_, LangPackKey } from "../../../lib/langPack";
const MAX_FOLDER_NAME_LENGTH = 12;
@@ -38,7 +39,8 @@ 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.innerHTML = `Choose chats and types of chats that will
appear and never appear in this folder.`;
+ this.caption.append(i18n(`Choose chats and types of chats that will
+appear and never appear in this folder.`));
this.stickerContainer = document.createElement('div');
this.stickerContainer.classList.add('sticker-container');
@@ -72,13 +74,13 @@ export default class AppEditFolderTab extends SliderSuperTab {
inputWrapper.append(this.nameInputField.container);
- const generateList = (className: string, h2Text: string, 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: string}[], to: any) => {
const container = document.createElement('div');
container.classList.add('folder-list', className);
const h2 = document.createElement('div');
h2.classList.add('sidebar-left-h2');
- h2.innerHTML = h2Text;
+ i18n_({element: h2, key: h2Text});
const categories = document.createElement('div');
categories.classList.add('folder-categories');
@@ -102,46 +104,46 @@ export default class AppEditFolderTab extends SliderSuperTab {
return container;
};
- this.include_peers = generateList('folder-list-included', 'Included chats', [{
+ this.include_peers = generateList('folder-list-included', 'ChatList.Filter.Include.Header', [{
icon: 'add primary',
- text: 'Add Chats',
+ text: 'ChatList.Filter.Include.AddChat',
withRipple: true
}, {
- text: 'Contacts',
+ text: 'ChatList.Filter.Contacts',
icon: 'newprivate',
name: 'contacts'
}, {
- text: 'Non-Contacts',
+ text: 'ChatList.Filter.NonContacts',
icon: 'noncontacts',
name: 'non_contacts'
}, {
- text: 'Groups',
+ text: 'ChatList.Filter.Groups',
icon: 'group',
name: 'groups'
}, {
- text: 'Channels',
+ text: 'ChatList.Filter.Channels',
icon: 'channel',
name: 'broadcasts'
}, {
- text: 'Bots',
+ text: 'ChatList.Filter.Bots',
icon: 'bots',
name: 'bots'
}], this.flags);
- this.exclude_peers = generateList('folder-list-excluded', 'Excluded chats', [{
+ this.exclude_peers = generateList('folder-list-excluded', 'ChatList.Filter.Exclude.Header', [{
icon: 'minus primary',
- text: 'Remove Chats',
+ text: 'ChatList.Filter.Exclude.AddChat',
withRipple: true
}, {
- text: 'Muted',
+ text: 'ChatList.Filter.MutedChats',
icon: 'mute',
name: 'exclude_muted'
}, {
- text: 'Archived',
+ text: 'ChatList.Filter.Archive',
icon: 'archive',
name: 'exclude_archived'
}, {
- text: 'Read',
+ text: 'ChatList.Filter.ReadChats',
icon: 'readchats',
name: 'exclude_read'
}], this.flags);
@@ -225,7 +227,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
private onCreateOpen() {
this.caption.style.display = '';
- this.title.innerText = 'New Folder';
+ this.setTitle('New Folder');
this.menuBtn.classList.add('hide');
this.confirmBtn.classList.remove('hide');
this.nameInputField.value = '';
@@ -238,7 +240,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
private onEditOpen() {
this.caption.style.display = 'none';
- this.title.innerText = this.type === 'create' ? 'New Folder' : 'Edit Folder';
+ this.setTitle(this.type === 'create' ? 'New Folder' : 'Edit Folder');
if(this.type === 'edit') {
this.menuBtn.classList.remove('hide');
diff --git a/src/components/sidebarLeft/tabs/editProfile.ts b/src/components/sidebarLeft/tabs/editProfile.ts
index d1a7b98b..02633838 100644
--- a/src/components/sidebarLeft/tabs/editProfile.ts
+++ b/src/components/sidebarLeft/tabs/editProfile.ts
@@ -5,7 +5,7 @@ import { SliderSuperTab } from "../../slider";
import { attachClickEvent } from "../../../helpers/dom";
import EditPeer from "../../editPeer";
import { UsernameInputField } from "../../usernameInputField";
-import { i18n_ } from "../../../lib/langPack";
+import { i18n, i18n_ } from "../../../lib/langPack";
// TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист)
@@ -93,8 +93,21 @@ export default class AppEditProfileTab extends SliderSuperTab {
const caption = document.createElement('div');
caption.classList.add('caption');
- caption.innerHTML = `You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.
You can use a-z, 0-9 and underscores. Minimum length is 5 characters.
This link opens a chat with you:
-
`;
+ caption.append(i18n('UsernameSettings.ChangeDescription'));
+ 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:'));
+
+ const profileUrlAnchor = this.profileUrlAnchor = document.createElement('a');
+ profileUrlAnchor.classList.add('profile-url');
+ profileUrlAnchor.href = '#';
+ profileUrlAnchor.target = '_blank';
+
+ profileUrlContainer.append(profileUrlAnchor);
+
+ caption.append(profileUrlContainer);
this.profileUrlContainer = caption.querySelector('.profile-url-container');
this.profileUrlAnchor = this.profileUrlContainer.lastElementChild as HTMLAnchorElement;
diff --git a/src/components/sidebarLeft/tabs/includedChats.ts b/src/components/sidebarLeft/tabs/includedChats.ts
index b7eb226c..6f82cfeb 100644
--- a/src/components/sidebarLeft/tabs/includedChats.ts
+++ b/src/components/sidebarLeft/tabs/includedChats.ts
@@ -10,6 +10,7 @@ import ButtonIcon from "../../buttonIcon";
import CheckboxField from "../../checkboxField";
import Button from "../../button";
import AppEditFolderTab from "./editFolder";
+import { i18n, LangPackKey, _i18n } from "../../../lib/langPack";
export default class AppIncludedChatsTab extends SliderSuperTab {
private editFolderTab: AppEditFolderTab;
@@ -123,7 +124,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
dom.containerEl.append(this.checkbox(selected));
if(selected) dom.listEl.classList.add('active');
- let subtitle = '';
+ let subtitle: LangPackKey;
if(peerId > 0) {
if(peerId === rootScope.myId) {
@@ -137,7 +138,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
subtitle = appPeersManager.isBroadcast(peerId) ? 'Channel' : 'Group';
}
- dom.lastMessageSpan.innerHTML = subtitle;
+ _i18n(dom.lastMessageSpan, subtitle);
});
};
@@ -148,14 +149,14 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
}
this.confirmBtn.style.display = this.type === 'excluded' ? '' : 'none';
- this.title.innerText = this.type === 'included' ? 'Included Chats' : 'Excluded Chats';
+ this.setTitle(this.type === 'included' ? 'Included Chats' : 'Excluded Chats');
const filter = this.filter;
const fragment = document.createDocumentFragment();
const dd = document.createElement('div');
dd.classList.add('sidebar-left-h2');
- dd.innerText = 'Chat types';
+ _i18n(dd, 'ChatList.Add.TopSeparator');
const categories = document.createElement('div');
categories.classList.add('folder-categories');
@@ -163,17 +164,17 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
let details: {[flag: string]: {ico: string, text: string}};
if(this.type === 'excluded') {
details = {
- exclude_muted: {ico: 'mute', text: 'Muted'},
- exclude_archived: {ico: 'archive', text: 'Archived'},
- exclude_read: {ico: 'readchats', text: 'Read'}
+ exclude_muted: {ico: 'mute', text: 'ChatList.Filter.MutedChats'},
+ exclude_archived: {ico: 'archive', text: 'ChatList.Filter.Archive'},
+ exclude_read: {ico: 'readchats', text: 'ChatList.Filter.ReadChats'}
};
} else {
details = {
- contacts: {ico: 'newprivate', text: 'Contacts'},
- non_contacts: {ico: 'noncontacts', text: 'Non-Contacts'},
- groups: {ico: 'group', text: 'Groups'},
- broadcasts: {ico: 'newchannel', text: 'Channels'},
- bots: {ico: 'bots', text: 'Bots'}
+ contacts: {ico: 'newprivate', text: 'ChatList.Filter.Contacts'},
+ non_contacts: {ico: 'noncontacts', text: 'ChatList.Filter.NonContacts'},
+ groups: {ico: 'group', text: 'ChatList.Filter.Groups'},
+ broadcasts: {ico: 'newchannel', text: 'ChatList.Filter.Channels'},
+ bots: {ico: 'bots', text: 'ChatList.Filter.Bots'}
};
}
@@ -191,7 +192,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
const d = document.createElement('div');
d.classList.add('sidebar-left-h2');
- d.innerText = 'Chats';
+ _i18n(d, 'ChatList.Add.BottomSeparator');
fragment.append(dd, categories, hr, d);
@@ -210,7 +211,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
const _add = this.selector.add.bind(this.selector);
this.selector.add = (peerId, title, scroll) => {
- const div = _add(peerId, details[peerId]?.text, scroll);
+ const div = _add(peerId, details[peerId] ? i18n(details[peerId].text) : undefined, scroll);
if(details[peerId]) {
div.querySelector('avatar-element').classList.add('tgico-' + details[peerId].ico);
}
@@ -256,4 +257,4 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
return super.open();
}
-}
\ No newline at end of file
+}
diff --git a/src/components/sidebarLeft/tabs/language.ts b/src/components/sidebarLeft/tabs/language.ts
new file mode 100644
index 00000000..d687649c
--- /dev/null
+++ b/src/components/sidebarLeft/tabs/language.ts
@@ -0,0 +1,107 @@
+import { SettingSection } from "..";
+import { randomLong } from "../../../helpers/random";
+import I18n from "../../../lib/langPack";
+import RadioField from "../../radioField";
+import Row, { RadioFormFromRows } from "../../row";
+import { SliderSuperTab } from "../../slider"
+
+export default class AppLanguageTab extends SliderSuperTab {
+ protected init() {
+ this.container.classList.add('language-container');
+ this.setTitle('Telegram.LanguageViewController');
+
+ const section = new SettingSection({});
+
+ const radioRows: Map = new Map();
+
+ let r = [{
+ code: 'en',
+ text: 'English',
+ subtitle: 'English'
+ }, {
+ code: 'be',
+ text: 'Belarusian',
+ subtitle: 'Беларуская'
+ }, {
+ code: 'ca',
+ text: 'Catalan',
+ subtitle: 'Català'
+ }, {
+ code: 'nl',
+ text: 'Dutch',
+ subtitle: 'Nederlands'
+ }, {
+ code: 'fr',
+ text: 'French',
+ subtitle: 'Français'
+ }, {
+ code: 'de',
+ text: 'German',
+ subtitle: 'Deutsch'
+ }, {
+ code: 'it',
+ text: 'Italian',
+ subtitle: 'Italiano'
+ }, {
+ code: 'ms',
+ text: 'Malay',
+ subtitle: 'Bahasa Melayu'
+ }, {
+ code: 'pl',
+ text: 'Polish',
+ subtitle: 'Polski'
+ }, {
+ code: 'pt',
+ text: 'Portuguese (Brazil)',
+ subtitle: 'Português (Brasil)'
+ }, {
+ code: 'ru',
+ text: 'Russian',
+ subtitle: 'Русский'
+ }, {
+ code: 'es',
+ text: 'Spanish',
+ subtitle: 'Español'
+ }, {
+ code: 'tr',
+ text: 'Turkish',
+ subtitle: 'Türkçe'
+ }, {
+ code: 'uk',
+ text: 'Ukrainian',
+ subtitle: 'Українська'
+ }];
+
+ const random = randomLong();
+ r.forEach(({code, text, subtitle}) => {
+ const row = new Row({
+ radioField: new RadioField({
+ text,
+ name: random,
+ value: code
+ }),
+ subtitle
+ });
+
+ radioRows.set(code, row);
+ });
+
+ const form = RadioFormFromRows([...radioRows.values()], (value) => {
+ I18n.getLangPack(value);
+ });
+
+ I18n.getCacheLangPack().then(langPack => {
+ const row = radioRows.get(langPack.lang_code);
+ if(!row) {
+ console.error('no row', row, langPack);
+ return;
+ }
+
+ row.radioField.setValueSilently(true);
+ });
+
+ section.content.append(form);
+
+ this.scrollable.append(section.container);
+ }
+}
diff --git a/src/components/sidebarLeft/tabs/settings.ts b/src/components/sidebarLeft/tabs/settings.ts
index 221d32de..5abf1de6 100644
--- a/src/components/sidebarLeft/tabs/settings.ts
+++ b/src/components/sidebarLeft/tabs/settings.ts
@@ -10,6 +10,7 @@ import AppEditProfileTab from "./editProfile";
import AppChatFoldersTab from "./chatFolders";
import AppNotificationsTab from "./notifications";
import PeerTitle from "../../peerTitle";
+import AppLanguageTab from "./language";
//import AppMediaViewer from "../../appMediaViewerNew";
export default class AppSettingsTab extends SliderSuperTab {
@@ -28,7 +29,7 @@ export default class AppSettingsTab extends SliderSuperTab {
init() {
this.container.classList.add('settings-container');
- this.title.innerText = 'Settings';
+ this.setTitle('Settings');
const btnMenu = ButtonMenuToggle({}, 'bottom-left', [{
icon: 'logout',
@@ -130,6 +131,10 @@ export default class AppSettingsTab extends SliderSuperTab {
this.buttons.privacy.addEventListener('click', () => {
new AppPrivacyAndSecurityTab(this.slider).open();
});
+
+ this.buttons.language.addEventListener('click', () => {
+ new AppLanguageTab(this.slider).open();
+ });
}
public fillElements() {
diff --git a/src/components/slider.ts b/src/components/slider.ts
index 31855a69..c84939fa 100644
--- a/src/components/slider.ts
+++ b/src/components/slider.ts
@@ -3,6 +3,7 @@ import { horizontalMenu } from "./horizontalMenu";
import { TransitionSlider } from "./transition";
import appNavigationController, { NavigationItem } from "./appNavigationController";
import SliderSuperTab, { SliderSuperTabConstructable, SliderTab } from "./sliderTab";
+import { safeAssign } from "../helpers/object";
const TRANSITION_TIME = 250;
@@ -24,7 +25,7 @@ export default class SidebarSlider {
canHideFirst?: SidebarSlider['canHideFirst'],
navigationType: SidebarSlider['navigationType']
}) {
- Object.assign(this, options);
+ safeAssign(this, options);
if(!this.tabs) {
this.tabs = new Map();
diff --git a/src/components/usernameInputField.ts b/src/components/usernameInputField.ts
index a2d0e8bc..c1fb8e73 100644
--- a/src/components/usernameInputField.ts
+++ b/src/components/usernameInputField.ts
@@ -1,6 +1,7 @@
import ListenerSetter from "../helpers/listenerSetter";
import { debounce } from "../helpers/schedulers";
import appChatsManager from "../lib/appManagers/appChatsManager";
+import { LangPackKey } from "../lib/langPack";
import apiManager from "../lib/mtproto/mtprotoworker";
import RichTextProcessor from "../lib/richtextprocessor";
import InputField, { InputFieldOptions, InputState } from "./inputField";
@@ -12,9 +13,9 @@ export class UsernameInputField extends InputField {
peerId: number,
listenerSetter: ListenerSetter,
onChange?: () => void,
- invalidText: string,
- takenText: string,
- availableText: string,
+ invalidText: LangPackKey,
+ takenText: LangPackKey,
+ availableText: LangPackKey,
head?: string
};
diff --git a/src/helpers/listLoader.ts b/src/helpers/listLoader.ts
index b938ea32..8196ef71 100644
--- a/src/helpers/listLoader.ts
+++ b/src/helpers/listLoader.ts
@@ -1,4 +1,5 @@
import Scrollable from "../components/scrollable";
+import { safeAssign } from "./object";
export default class ScrollableLoader {
public loading = false;
@@ -11,7 +12,7 @@ export default class ScrollableLoader {
scrollable: ScrollableLoader['scrollable'],
getPromise: ScrollableLoader['getPromise']
}) {
- Object.assign(this, options);
+ safeAssign(this, options);
options.scrollable.onScrolledBottom = () => {
this.load();
diff --git a/src/helpers/object.ts b/src/helpers/object.ts
index f8396428..14e4d812 100644
--- a/src/helpers/object.ts
+++ b/src/helpers/object.ts
@@ -120,3 +120,13 @@ export function validateInitObject(initObject: any, currentObject: any) {
}
}
}
+
+export function safeAssign(object: any, fromObject: any) {
+ if(!fromObject) return;
+
+ for(let i in fromObject) {
+ if(fromObject[i] !== undefined) {
+ object[i] = fromObject[i];
+ }
+ }
+}
diff --git a/src/index.hbs b/src/index.hbs
index 3f75e662..9f2a406c 100644
--- a/src/index.hbs
+++ b/src/index.hbs
@@ -99,16 +99,7 @@
diff --git a/src/lib/idb.ts b/src/lib/idb.ts
index c73d5c2a..e09b7e74 100644
--- a/src/lib/idb.ts
+++ b/src/lib/idb.ts
@@ -1,5 +1,6 @@
import Database from '../config/database';
import { blobConstruct } from '../helpers/blob';
+import { safeAssign } from '../helpers/object';
import { logger } from './logger';
/**
@@ -36,7 +37,7 @@ export default class IDBStorage {
public storeName: string;
constructor(options: IDBOptions) {
- Object.assign(this, options);
+ safeAssign(this, options);
this.openDatabase(true);
}
diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts
index 717df687..45e565bc 100644
--- a/src/lib/langPack.ts
+++ b/src/lib/langPack.ts
@@ -1,6 +1,8 @@
import { MOUNT_CLASS_TO } from "../config/debug";
-import { LangPackString } from "../layer";
+import { safeAssign } from "../helpers/object";
+import { LangPackDifference, LangPackString } from "../layer";
import apiManager from "./mtproto/mtprotoworker";
+import sessionStorage from "./sessionStorage";
export const langPack: {[actionType: string]: string} = {
"messageActionChatCreate": "created the group",
@@ -41,9 +43,15 @@ namespace Strings {
export type AccountSettings = 'AccountSettings.Filters' | 'AccountSettings.Notifications' | 'AccountSettings.PrivacyAndSecurity' | 'AccountSettings.Language' | 'AccountSettings.Bio';
- export type Telegram = 'Telegram.GeneralSettingsViewController' | 'Telegram.NotificationSettingsViewController';
+ export type Telegram = 'Telegram.GeneralSettingsViewController' | 'Telegram.NotificationSettingsViewController' | 'Telegram.LanguageViewController';
- export type ChatFilters = 'ChatList.Filter.Header' | 'ChatList.Filter.NewTitle' | 'ChatList.Filter.List.Header' | 'ChatList.Filter.Recommended.Header' | 'ChatList.Filter.Recommended.Add' | 'ChatList.Filter.List.Title';
+ 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';
@@ -51,37 +59,68 @@ namespace Strings {
export type Suggest = 'Suggest.Localization.Other';
- export type LangPackKey = string | AccountSettings | EditAccount | Telegram | ChatFilters | LoginRegister | Bio | AutoDownloadSettings | DataAndStorage | Suggest;
+ export type UsernameSettings = 'UsernameSettings.ChangeDescription';
+
+ export type LangPackKey = string | AccountSettings | EditAccount | Telegram | ChatList | LoginRegister | Bio | AutoDownloadSettings | DataAndStorage | Suggest | UsernameSettings;
}
export type LangPackKey = Strings.LangPackKey;
namespace I18n {
- let strings: Partial<{[key in LangPackKey]: LangPackString}> = {};
+ export const strings: Map = new Map();
+
+ let lastRequestedLangCode: string;
+ export function getCacheLangPack(): Promise {
+ return sessionStorage.get('langPack').then((langPack: LangPackDifference) => {
+ if(!langPack) {
+ return getLangPack('en');
+ }
+
+ if(!lastRequestedLangCode) {
+ lastRequestedLangCode = langPack.lang_code;
+ }
+
+ applyLangPack(langPack);
+ return langPack;
+ });
+ }
export function getLangPack(langCode: string) {
+ lastRequestedLangCode = langCode;
return apiManager.invokeApi('langpack.getLangPack', {
lang_code: langCode,
lang_pack: 'macos'
}).then(langPack => {
- strings = {};
- for(const string of langPack.strings) {
- strings[string.key as LangPackKey] = string;
- }
+ return sessionStorage.set({langPack}).then(() => {
+ applyLangPack(langPack);
+ return langPack;
+ });
+ });
+ }
+
+ export function applyLangPack(langPack: LangPackDifference) {
+ if(langPack.lang_code !== lastRequestedLangCode) {
+ return;
+ }
- const elements = Array.from(document.querySelectorAll(`.i18n`)) as HTMLElement[];
- elements.forEach(element => {
- const instance = weakMap.get(element);
+ strings.clear();
- if(instance) {
- instance.update();
- }
- });
+ for(const string of langPack.strings) {
+ strings.set(string.key as LangPackKey, string);
+ }
+
+ const elements = Array.from(document.querySelectorAll(`.i18n`)) as HTMLElement[];
+ elements.forEach(element => {
+ const instance = weakMap.get(element);
+
+ if(instance) {
+ instance.update();
+ }
});
}
export function getString(key: LangPackKey, args?: any[]) {
- const str = strings[key];
+ const str = strings.get(key);
let out = '';
if(str) {
@@ -103,7 +142,7 @@ namespace I18n {
export type IntlElementOptions = {
element?: HTMLElement,
- property?: 'innerHTML' | 'placeholder'
+ property?: 'innerText' | 'innerHTML' | 'placeholder'
key: LangPackKey,
args?: any[]
};
@@ -111,7 +150,7 @@ namespace I18n {
public element: IntlElementOptions['element'];
public key: IntlElementOptions['key'];
public args: IntlElementOptions['args'];
- public property: IntlElementOptions['property'] = 'innerHTML';
+ public property: IntlElementOptions['property'] = 'innerText';
constructor(options: IntlElementOptions) {
this.element = options.element || document.createElement('span');
@@ -122,9 +161,7 @@ namespace I18n {
}
public update(options?: IntlElementOptions) {
- if(options) {
- Object.assign(this, options);
- }
+ safeAssign(this, options);
(this.element as any)[this.property] = getString(this.key, this.args);
}
@@ -137,6 +174,10 @@ namespace I18n {
export function i18n_(options: IntlElementOptions) {
return new IntlElement(options).element;
}
+
+ export function _i18n(element: HTMLElement, key: LangPackKey, args?: any[], property?: IntlElementOptions['property']) {
+ return new IntlElement({element, key, args, property}).element;
+ }
}
export {I18n};
@@ -148,4 +189,7 @@ export {i18n};
const i18n_ = I18n.i18n_;
export {i18n_};
+const _i18n = I18n._i18n;
+export {_i18n};
+
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.I18n = I18n);
diff --git a/src/lib/sessionStorage.ts b/src/lib/sessionStorage.ts
index d5517773..b992aaae 100644
--- a/src/lib/sessionStorage.ts
+++ b/src/lib/sessionStorage.ts
@@ -1,4 +1,5 @@
import { MOUNT_CLASS_TO } from '../config/debug';
+import { LangPackDifference } from '../layer';
import type { State } from './appManagers/appStateManager';
import AppStorage from './storage';
@@ -19,6 +20,7 @@ const sessionStorage = new AppStorage<{
top: number
}
},
+ langPack: LangPackDifference
} & State>({
storeName: 'session'
});
diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss
index c5723f3e..69629b3d 100644
--- a/src/scss/partials/_leftSidebar.scss
+++ b/src/scss/partials/_leftSidebar.scss
@@ -569,7 +569,7 @@
align-items: center;
margin: 15px auto 1rem;
border-radius: 30px;
- padding: 0 12px;
+ padding: 0 24px 0 12px;
display: flex;
}
@@ -581,8 +581,9 @@
.row {
.btn-primary {
height: 30px;
+ padding: 0 12px;
font-size: 15px;
- width: 52px;
+ width: auto;
transition: width 0.2s;
margin: 0;
position: absolute;