Language tab

Save language
This commit is contained in:
Eduard Kuzmenko 2021-03-21 18:36:14 +04:00
parent 2de727d616
commit eab07f7ebc
19 changed files with 341 additions and 129 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
this.archivedCount = document.createElement('span');
this.archivedCount.className = 'archived-count badge badge-24 badge-gray';
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();
});
btnArchive.element.append(this.archivedCount);
[this.newButtons.privateChat, this.buttons.contacts].forEach(btn => {
attachClickEvent(btn, (e) => {
new AppContactsTab(this).open();
});
});
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);

View File

@ -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<br>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');

View File

@ -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.<br><br>You can use a-z, 0-9 and underscores. Minimum length is 5 characters.<br><br><div class="profile-url-container">This link opens a chat with you:
<br><a class="profile-url" href="#" target="_blank"></a></div>`;
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;

View File

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

View File

@ -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<string, Row> = 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);
}
}

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

@ -99,16 +99,7 @@
<div class="sidebar-header">
<div class="sidebar-header__btn-container">
<div class="animated-menu-icon"></div>
<div class="btn-icon btn-menu-toggle rp sidebar-tools-button is-visible">
<div class="btn-menu bottom-right">
<div class="btn-menu-item menu-newGroup tgico-newgroup rp">New Group</div>
<div class="btn-menu-item menu-contacts tgico-user rp">Contacts</div>
<div class="btn-menu-item menu-archived tgico-archive rp">Archived <span class="archived-count badge badge-24 badge-gray"></span></div>
<div class="btn-menu-item menu-saved tgico-savedmessages rp">Saved</div>
<div class="btn-menu-item menu-settings tgico-settings rp">Settings</div>
<div class="btn-menu-item menu-help tgico-help rp btn-disabled">Help</div>
</div>
</div>
<div class="btn-icon btn-menu-toggle rp sidebar-tools-button is-visible"></div>
<div class="btn-icon rp sidebar-back-button"></div>
</div>
</div>

View File

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

View File

@ -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<LangPackKey, LangPackString> = new Map();
let lastRequestedLangCode: string;
export function getCacheLangPack(): Promise<LangPackDifference> {
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;
}
const elements = Array.from(document.querySelectorAll(`.i18n`)) as HTMLElement[];
elements.forEach(element => {
const instance = weakMap.get(element);
if(instance) {
instance.update();
}
return sessionStorage.set({langPack}).then(() => {
applyLangPack(langPack);
return langPack;
});
});
}
export function applyLangPack(langPack: LangPackDifference) {
if(langPack.lang_code !== lastRequestedLangCode) {
return;
}
strings.clear();
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);

View File

@ -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'
});

View File

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