diff --git a/src/components/button.ts b/src/components/button.ts
index fe4064b5..3a8b32f7 100644
--- a/src/components/button.ts
+++ b/src/components/button.ts
@@ -1,6 +1,7 @@
+import { i18n, LangPackKey } from "../lib/langPack";
import { ripple } from "./ripple";
-const Button = (className: string, options: Partial<{noRipple: true, onlyMobile: true, icon: string, rippleSquare: true, text: string, disabled: boolean}> = {}) => {
+const Button = (className: string, options: Partial<{noRipple: true, onlyMobile: true, icon: string, rippleSquare: true, text: LangPackKey, disabled: boolean}> = {}) => {
const button = document.createElement('button');
button.className = className + (options.icon ? ' tgico-' + options.icon : '');
@@ -21,7 +22,7 @@ const Button = (className: string, options: Partial<{noRipple: true, onlyMobile:
}
if(options.text) {
- button.append(options.text);
+ button.append(i18n(options.text));
}
return button;
diff --git a/src/components/buttonMenu.ts b/src/components/buttonMenu.ts
index 3e759d98..1f7e208c 100644
--- a/src/components/buttonMenu.ts
+++ b/src/components/buttonMenu.ts
@@ -1,11 +1,12 @@
import { attachClickEvent, AttachClickOptions, cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
import ListenerSetter from "../helpers/listenerSetter";
+import { i18n, LangPackKey } from "../lib/langPack";
import { closeBtnMenu } from "./misc";
import { ripple } from "./ripple";
export type ButtonMenuItemOptions = {
icon: string,
- text: string,
+ text: LangPackKey,
onClick: (e: MouseEvent | TouchEvent) => void,
element?: HTMLElement,
options?: AttachClickOptions
@@ -18,7 +19,7 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
const {icon, text, onClick} = options;
const el = document.createElement('div');
el.className = 'btn-menu-item tgico-' + icon;
- el.innerText = text;
+ el.append(i18n(text));
ripple(el);
diff --git a/src/components/inputField.ts b/src/components/inputField.ts
index 3dc9d139..fceef259 100644
--- a/src/components/inputField.ts
+++ b/src/components/inputField.ts
@@ -1,6 +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 RichTextProcessor from "../lib/richtextprocessor";
let init = () => {
@@ -59,7 +60,7 @@ export enum InputState {
export type InputFieldOptions = {
placeholder?: string,
- label?: string,
+ label?: LangPackKey,
name?: string,
maxLength?: number,
showLengthOn?: number,
@@ -97,7 +98,6 @@ class InputField {
this.container.innerHTML = `
- ${label ? `` : ''}
`;
input = this.container.firstElementChild as HTMLElement;
@@ -135,7 +135,6 @@ class InputField {
} else {
this.container.innerHTML = `
- ${label ? `` : ''}
`;
input = this.container.firstElementChild as HTMLElement;
@@ -143,7 +142,9 @@ class InputField {
}
if(label) {
- this.label = this.container.lastElementChild as HTMLLabelElement;
+ this.label = document.createElement('label');
+ i18n_({element: this.label, key: label});
+ this.container.append(this.label);
}
let processInput: () => void;
diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts
index b1e21ce6..2df87c85 100644
--- a/src/components/sidebarLeft/index.ts
+++ b/src/components/sidebarLeft/index.ts
@@ -23,6 +23,7 @@ import AppNewChannelTab from "./tabs/newChannel";
import AppContactsTab from "./tabs/contacts";
import AppArchivedTab from "./tabs/archivedTab";
import AppAddMembersTab from "./tabs/addMembers";
+import { i18n_, LangPackKey } from "../../lib/langPack";
export const LEFT_COLUMN_ACTIVE_CLASSNAME = 'is-left-column-shown';
@@ -413,7 +414,7 @@ export class SettingSection {
public caption: HTMLElement;
constructor(options: {
- name?: string,
+ name?: LangPackKey,
caption?: string,
noDelimiter?: boolean
}) {
@@ -432,7 +433,7 @@ export class SettingSection {
if(options.name) {
this.title = document.createElement('div');
this.title.classList.add('sidebar-left-h2', 'sidebar-left-section-name');
- this.title.innerHTML = options.name;
+ i18n_({element: this.title, key: options.name});
this.content.append(this.title);
}
@@ -451,7 +452,7 @@ export class SettingSection {
}
}
-export const generateSection = (appendTo: Scrollable, name?: string, caption?: string) => {
+export const generateSection = (appendTo: Scrollable, name?: LangPackKey, caption?: string) => {
const section = new SettingSection({name, caption});
appendTo.append(section.container);
return section.content;
diff --git a/src/components/sidebarLeft/tabs/chatFolders.ts b/src/components/sidebarLeft/tabs/chatFolders.ts
index 364208f6..3e857364 100644
--- a/src/components/sidebarLeft/tabs/chatFolders.ts
+++ b/src/components/sidebarLeft/tabs/chatFolders.ts
@@ -16,6 +16,7 @@ import rootScope from "../../../lib/rootScope";
import AppEditFolderTab from "./editFolder";
import Row from "../../row";
import { SettingSection } from "..";
+import { i18n_ } from "../../../lib/langPack";
export default class AppChatFoldersTab extends SliderSuperTab {
private createFolderBtn: HTMLElement;
@@ -101,7 +102,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
protected init() {
this.container.classList.add('chat-folders-container');
- this.title.innerText = 'Chat Folders';
+ this.setTitle('ChatList.Filter.List.Title');
this.scrollable.container.classList.add('chat-folders');
@@ -110,20 +111,20 @@ export default class AppChatFoldersTab extends SliderSuperTab {
const caption = document.createElement('div');
caption.classList.add('caption');
- caption.innerHTML = `Create folders for different groups of chats
and quickly switch between them.`;
+ i18n_({element: caption, key: 'ChatList.Filter.Header'});
this.createFolderBtn = Button('btn-primary btn-color-primary btn-create-folder', {
- text: 'Create Folder',
+ text: 'ChatList.Filter.NewTitle',
icon: 'add'
});
this.foldersSection = new SettingSection({
- name: 'Folders'
+ name: 'ChatList.Filter.List.Header'
});
this.foldersSection.container.style.display = 'none';
this.suggestedSection = new SettingSection({
- name: 'Recommended folders'
+ name: 'ChatList.Filter.Recommended.Header'
});
this.suggestedSection.container.style.display = 'none';
@@ -207,9 +208,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
suggestedFilters.forEach(filter => {
const div = this.renderFolder(filter);
- const button = document.createElement('button');
- button.classList.add('btn-primary', 'btn-color-primary');
- button.innerText = 'Add';
+ const button = Button('btn-primary btn-color-primary', {text: 'ChatList.Filter.Recommended.Add'});
div.append(button);
this.suggestedSection.content.append(div);
diff --git a/src/components/sidebarLeft/tabs/editProfile.ts b/src/components/sidebarLeft/tabs/editProfile.ts
index b8201de6..d1a7b98b 100644
--- a/src/components/sidebarLeft/tabs/editProfile.ts
+++ b/src/components/sidebarLeft/tabs/editProfile.ts
@@ -5,6 +5,7 @@ import { SliderSuperTab } from "../../slider";
import { attachClickEvent } from "../../../helpers/dom";
import EditPeer from "../../editPeer";
import { UsernameInputField } from "../../usernameInputField";
+import { i18n_ } from "../../../lib/langPack";
// TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист)
@@ -21,7 +22,7 @@ export default class AppEditProfileTab extends SliderSuperTab {
protected async init() {
this.container.classList.add('edit-profile-container');
- this.title.innerText = 'Edit Profile';
+ this.setTitle('EditAccount.Title');
const inputFields: InputField[] = [];
@@ -30,17 +31,17 @@ export default class AppEditProfileTab extends SliderSuperTab {
inputWrapper.classList.add('input-wrapper');
this.firstNameInputField = new InputField({
- label: 'Name',
+ label: 'Login.Register.FirstName.Placeholder',
name: 'first-name',
maxLength: 70
});
this.lastNameInputField = new InputField({
- label: 'Last Name',
+ label: 'Login.Register.LastName.Placeholder',
name: 'last-name',
maxLength: 64
});
this.bioInputField = new InputField({
- label: 'Bio (optional)',
+ label: 'AccountSettings.Bio',
name: 'bio',
maxLength: 70
});
@@ -49,7 +50,7 @@ export default class AppEditProfileTab extends SliderSuperTab {
const caption = document.createElement('div');
caption.classList.add('caption');
- caption.innerHTML = 'Any details such as age, occupation or city. Example:
23 y.o. designer from San Francisco.';
+ i18n_({element: caption, key: 'Bio.Description'});
inputFields.push(this.firstNameInputField, this.lastNameInputField, this.bioInputField);
this.scrollable.append(inputWrapper, caption);
@@ -68,14 +69,14 @@ export default class AppEditProfileTab extends SliderSuperTab {
{
const h2 = document.createElement('div');
h2.classList.add('sidebar-left-h2');
- h2.innerText = 'Username';
+ i18n_({element: h2, key: 'EditAccount.Username'});
const inputWrapper = document.createElement('div');
inputWrapper.classList.add('input-wrapper');
this.usernameInputField = new UsernameInputField({
peerId: 0,
- label: 'Username (optional)',
+ label: 'EditAccount.Username',
name: 'username',
plainText: true,
listenerSetter: this.listenerSetter,
diff --git a/src/components/sidebarLeft/tabs/settings.ts b/src/components/sidebarLeft/tabs/settings.ts
index aaaea64b..221d32de 100644
--- a/src/components/sidebarLeft/tabs/settings.ts
+++ b/src/components/sidebarLeft/tabs/settings.ts
@@ -32,7 +32,7 @@ export default class AppSettingsTab extends SliderSuperTab {
const btnMenu = ButtonMenuToggle({}, 'bottom-left', [{
icon: 'logout',
- text: 'Log Out',
+ text: 'EditAccount.Logout',
onClick: () => {
apiManager.logOut();
}
@@ -96,12 +96,12 @@ export default class AppSettingsTab extends SliderSuperTab {
buttonsDiv.classList.add('profile-buttons');
const className = 'profile-button btn-primary btn-transparent';
- buttonsDiv.append(this.buttons.edit = Button(className, {icon: 'edit', text: 'Edit Profile'}));
- buttonsDiv.append(this.buttons.folders = Button(className, {icon: 'folder', text: 'Chat Folders'}));
- buttonsDiv.append(this.buttons.general = Button(className, {icon: 'settings', text: 'General Settings'}));
- buttonsDiv.append(this.buttons.notifications = Button(className, {icon: 'unmute', text: 'Notifications'}));
- buttonsDiv.append(this.buttons.privacy = Button(className, {icon: 'lock', text: 'Privacy and Security'}));
- buttonsDiv.append(this.buttons.language = Button(className, {icon: 'language', text: 'Language', disabled: true}));
+ buttonsDiv.append(this.buttons.edit = Button(className, {icon: 'edit', text: 'EditAccount.Title'}));
+ buttonsDiv.append(this.buttons.folders = Button(className, {icon: 'folder', text: 'AccountSettings.Filters'}));
+ buttonsDiv.append(this.buttons.general = Button(className, {icon: 'settings', text: 'Telegram.GeneralSettingsViewController'}));
+ buttonsDiv.append(this.buttons.notifications = Button(className, {icon: 'unmute', text: 'AccountSettings.Notifications'}));
+ buttonsDiv.append(this.buttons.privacy = Button(className, {icon: 'lock', text: 'AccountSettings.PrivacyAndSecurity'}));
+ buttonsDiv.append(this.buttons.language = Button(className, {icon: 'language', text: 'AccountSettings.Language'}));
this.scrollable.append(this.avatarElem, this.nameDiv, this.phoneDiv, buttonsDiv);
this.scrollable.container.classList.add('profile-content-wrapper');
diff --git a/src/components/sliderTab.ts b/src/components/sliderTab.ts
index f4372b92..ca4abaff 100644
--- a/src/components/sliderTab.ts
+++ b/src/components/sliderTab.ts
@@ -1,5 +1,6 @@
import EventListenerBase from "../helpers/eventListenerBase";
import ListenerSetter from "../helpers/listenerSetter";
+import { i18n, LangPackKey } from "../lib/langPack";
import ButtonIcon from "./buttonIcon";
import Scrollable from "./scrollable";
import SidebarSlider from "./slider";
@@ -92,6 +93,11 @@ export default class SliderSuperTab implements SliderTab {
this.listenerSetter.removeAll();
}
}
+
+ protected setTitle(key: LangPackKey) {
+ this.title.innerHTML = '';
+ this.title.append(i18n(key));
+ }
}
export class SliderSuperTabEventable extends SliderSuperTab {
diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts
index 1b4c6eff..99950d4b 100644
--- a/src/lib/langPack.ts
+++ b/src/lib/langPack.ts
@@ -1,3 +1,4 @@
+import { MOUNT_CLASS_TO } from "../config/debug";
import { LangPackString } from "../layer";
import apiManager from "./mtproto/mtprotoworker";
@@ -31,8 +32,26 @@ export const langPack: {[actionType: string]: string} = {
"messageActionBotAllowed": "You allowed this bot to message you when logged in {}"
};
-export namespace Internationalization {
- let strings: {[key: string]: LangPackString} = {};
+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';
+
+ 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 LangPackKey = AccountSettings | EditAccount | Telegram | ChatFilters | LoginRegister | Bio | string;
+}
+
+export type LangPackKey = Strings.LangPackKey;
+
+namespace I18n {
+ let strings: Partial<{[key in LangPackKey]: LangPackString}> = {};
export function getLangPack(langCode: string) {
return apiManager.invokeApi('langpack.getLangPack', {
@@ -41,14 +60,86 @@ export namespace Internationalization {
}).then(langPack => {
strings = {};
for(const string of langPack.strings) {
- strings[string.key] = string;
+ 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();
+ }
+ });
});
}
- export function _(key: keyof typeof strings, ...args: any[]) {
- let str = strings[key];
+ export function getString(key: LangPackKey, args?: any[]) {
+ const str = strings[key];
+ let out = '';
+
+ if(str) {
+ if(str._ === 'langPackStringPluralized') {
+ out = str.one_value;
+ } else if(str._ === 'langPackString') {
+ out = str.value;
+ } else {
+ out = '[' + key + ']';
+ }
+ } else {
+ out = '[' + key + ']';
+ }
+
+ return out;
+ }
+
+ const weakMap: WeakMap = new WeakMap();
+
+ export type IntlElementOptions = {
+ element?: HTMLElement,
+ property?: 'innerHTML' | 'placeholder'
+ key: LangPackKey,
+ args?: any[]
+ };
+ export class IntlElement {
+ public element: IntlElementOptions['element'];
+ public key: IntlElementOptions['key'];
+ public args: IntlElementOptions['args'];
+ public property: IntlElementOptions['property'] = 'innerHTML';
+
+ constructor(options: IntlElementOptions) {
+ this.element = options.element || document.createElement('span');
+ this.element.classList.add('i18n');
+
+ this.update(options);
+ weakMap.set(this.element, this);
+ }
+
+ public update(options?: IntlElementOptions) {
+ if(options) {
+ Object.assign(this, options);
+ }
+
+ (this.element as any)[this.property] = getString(this.key, this.args);
+ }
+ }
+
+ export function i18n(key: LangPackKey, args?: any[]) {
+ return new IntlElement({key, args}).element;
+ }
- return str;
+ export function i18n_(options: IntlElementOptions) {
+ return new IntlElement(options).element;
}
}
+
+export {I18n};
+export default I18n;
+
+const i18n = I18n.i18n;
+export {i18n};
+
+const i18n_ = I18n.i18n_;
+export {i18n_};
+
+MOUNT_CLASS_TO && (MOUNT_CLASS_TO.I18n = I18n);