diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index 5377669f..31b1b378 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -11,6 +11,7 @@ import { FocusDirection } from "../helpers/fastSmoothScroll"; import CheckboxField from "./checkboxField"; import appProfileManager from "../lib/appManagers/appProfileManager"; import { safeAssign } from "../helpers/object"; +import { LangPackKey, _i18n } from "../lib/langPack"; type PeerType = 'contacts' | 'dialogs' | 'channelParticipants'; @@ -57,6 +58,8 @@ export default class AppSelectPeers { private tempIds: {[k in keyof AppSelectPeers['loadedWhat']]: number} = {}; private peerId = 0; + + private placeholder: LangPackKey; constructor(options: { appendTo: AppSelectPeers['appendTo'], @@ -69,6 +72,7 @@ export default class AppSelectPeers { multiSelect?: AppSelectPeers['multiSelect'], rippleEnabled?: boolean, avatarSize?: AppSelectPeers['avatarSize'], + placeholder?: LangPackKey }) { safeAssign(this, options); @@ -94,7 +98,12 @@ export default class AppSelectPeers { this.input = document.createElement('input'); this.input.classList.add('selector-search-input'); - this.input.placeholder = !this.peerType.includes('dialogs') ? 'Add People...' : 'Select chat'; + if(this.placeholder) { + _i18n(this.input, this.placeholder, undefined, 'placeholder'); + } else { + _i18n(this.input, !this.peerType.includes('dialogs') ? 'SendMessageTo' : 'SelectChat', undefined, 'placeholder'); + } + this.input.type = 'text'; if(this.multiSelect) { diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index 8de912b6..b02f3e2e 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -13,6 +13,7 @@ import PopupPinMessage from "../popups/unpinMessage"; import { copyTextToClipboard } from "../../helpers/clipboard"; import PopupSendNow from "../popups/sendNow"; import { toast } from "../toast"; +import I18n, { LangPackKey } from "../../lib/langPack"; export default class ChatContextMenu { private buttons: (ButtonMenuItemOptions & {verify: () => boolean, notDirect?: () => boolean, withSelection?: true})[]; @@ -150,19 +151,19 @@ export default class ChatContextMenu { private init() { this.buttons = [{ icon: 'send2', - text: 'Send Now', + text: 'Chat.Context.Scheduled.SendNow', onClick: this.onSendScheduledClick, verify: () => this.chat.type === 'scheduled' && !this.message.pFlags.is_outgoing }, { icon: 'send2', - text: 'Send Now selected', + text: 'Message.Context.Selection.SendNow', onClick: this.onSendScheduledClick, verify: () => this.chat.type === 'scheduled' && this.chat.selection.selectedMids.has(this.mid) && !this.chat.selection.selectionSendNowBtn.hasAttribute('disabled'), notDirect: () => true, withSelection: true }, { icon: 'schedule', - text: 'Reschedule', + text: 'Chat.Context.Scheduled.Reschedule', onClick: () => { this.chat.input.scheduleSending(() => { this.appMessagesManager.editMessage(this.message, this.message.message, { @@ -195,30 +196,30 @@ export default class ChatContextMenu { verify: () => !!this.message.message && !this.isTextSelected }, { icon: 'copy', - text: 'Copy Selected Text', + text: 'Chat.CopySelectedText', onClick: this.onCopyClick, verify: () => !!this.message.message && this.isTextSelected }, { icon: 'copy', - text: 'Copy selected', + text: 'Message.Context.Selection.Copy', onClick: this.onCopyClick, verify: () => this.chat.selection.selectedMids.has(this.mid) && !![...this.chat.selection.selectedMids].find(mid => !!this.chat.getMessage(mid).message), notDirect: () => true, withSelection: true }, { icon: 'copy', - text: 'Copy Link', + text: 'CopyLink', onClick: this.onCopyAnchorLinkClick, verify: () => this.isAnchorTarget, withSelection: true }, { icon: 'link', - text: 'Copy Link', + text: 'CopyLink', onClick: this.onCopyLinkClick, verify: () => this.appPeersManager.isChannel(this.peerId) && !this.message.pFlags.is_outgoing }, { icon: 'pin', - text: 'Pin', + text: 'Message.Context.Pin', onClick: this.onPinClick, verify: () => !this.message.pFlags.is_outgoing && this.message._ !== 'messageService' && @@ -227,12 +228,12 @@ export default class ChatContextMenu { this.chat.type !== 'scheduled', }, { icon: 'unpin', - text: 'Unpin', + text: 'Message.Context.Unpin', onClick: this.onUnpinClick, verify: () => this.message.pFlags.pinned && this.appPeersManager.canPinMessage(this.peerId), }, { icon: 'revote', - text: 'Revote', + text: 'Chat.Poll.Unvote', onClick: this.onRetractVote, verify: () => { const poll = this.message.media?.poll as Poll; @@ -241,7 +242,7 @@ export default class ChatContextMenu { cancelEvent: true */ }, { icon: 'stop', - text: 'Stop poll', + text: 'Chat.Poll.Stop', onClick: this.onStopPoll, verify: () => { const poll = this.message.media?.poll; @@ -255,7 +256,7 @@ export default class ChatContextMenu { verify: () => this.chat.type !== 'scheduled' && !this.message.pFlags.is_outgoing && this.message._ !== 'messageService' }, { icon: 'forward', - text: 'Forward selected', + text: 'Message.Context.Selection.Forward', onClick: this.onForwardClick, verify: () => this.chat.selection.selectionForwardBtn && this.chat.selection.selectedMids.has(this.mid) && @@ -264,14 +265,14 @@ export default class ChatContextMenu { withSelection: true }, { icon: 'select', - text: 'Select', + text: 'Message.Context.Select', onClick: this.onSelectClick, verify: () => !this.message.action && !this.chat.selection.selectedMids.has(this.mid) && this.isSelectable, notDirect: () => true, withSelection: true }, { icon: 'select', - text: 'Clear selection', + text: 'Message.Context.Selection.Clear', onClick: this.onClearSelectionClick, verify: () => this.chat.selection.selectedMids.has(this.mid), notDirect: () => true, @@ -283,7 +284,7 @@ export default class ChatContextMenu { verify: () => this.appMessagesManager.canDeleteMessage(this.message) }, { icon: 'delete danger', - text: 'Delete selected', + text: 'Message.Context.Selection.Delete', onClick: this.onDeleteClick, verify: () => this.chat.selection.selectedMids.has(this.mid) && !this.chat.selection.selectionDeleteBtn.hasAttribute('disabled'), notDirect: () => true, @@ -334,14 +335,17 @@ export default class ChatContextMenu { const username = this.appPeersManager.getPeerUsername(this.peerId); const msgId = this.appMessagesManager.getServerMessageId(this.mid); let url = 'https://t.me/'; + let key: LangPackKey; if(username) { url += username + '/' + msgId; - toast('Link copied to clipboard.'); + key = 'LinkCopied'; } else { url += 'c/' + Math.abs(this.peerId) + '/' + msgId; - toast('This link will only work for chat members.'); + key = 'LinkCopiedPrivateInfo'; } + toast(I18n.format(key, true)); + copyTextToClipboard(url); }; diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts index 5fd42ea2..f7d24a90 100644 --- a/src/components/chat/selection.ts +++ b/src/components/chat/selection.ts @@ -15,6 +15,7 @@ import ListenerSetter from "../../helpers/listenerSetter"; import PopupSendNow from "../popups/sendNow"; import appNavigationController from "../appNavigationController"; import { isMobileSafari } from "../../helpers/userAgent"; +import I18n, { i18n, _i18n } from "../../lib/langPack"; const MAX_SELECTION_LENGTH = 100; //const MIN_CLICK_MOVE = 32; // minimum bubble height @@ -214,7 +215,8 @@ export default class ChatSelection { private updateContainer(forceSelection = false) { if(!this.selectedMids.size && !forceSelection) return; - this.selectionCountEl.innerText = this.selectedMids.size + ' Message' + (this.selectedMids.size === 1 ? '' : 's'); + this.selectionCountEl.textContent = ''; + this.selectionCountEl.append(i18n('Chat.Selection.MessagesCount', [this.selectedMids.size])); let cantForward = !this.selectedMids.size, cantDelete = !this.selectedMids.size, cantSend = !this.selectedMids.size; for(const mid of this.selectedMids.values()) { @@ -348,7 +350,7 @@ export default class ChatSelection { if(this.chat.type === 'scheduled') { this.selectionSendNowBtn = Button('btn-primary btn-transparent btn-short text-bold selection-container-send', {icon: 'send2'}); - this.selectionSendNowBtn.append('Send Now'); + _i18n(this.selectionSendNowBtn, 'Chat.Context.Scheduled.SendNow'); this.listenerSetter.add(this.selectionSendNowBtn, 'click', () => { new PopupSendNow(this.bubbles.peerId, [...this.selectedMids], () => { this.cancelSelection(); @@ -356,7 +358,7 @@ export default class ChatSelection { }); } else { this.selectionForwardBtn = Button('btn-primary btn-transparent text-bold selection-container-forward', {icon: 'forward'}); - this.selectionForwardBtn.append('Forward'); + _i18n(this.selectionForwardBtn, 'Forward'); this.listenerSetter.add(this.selectionForwardBtn, 'click', () => { new PopupForward(this.bubbles.peerId, [...this.selectedMids], () => { this.cancelSelection(); @@ -365,7 +367,7 @@ export default class ChatSelection { } this.selectionDeleteBtn = Button('btn-primary btn-transparent danger text-bold selection-container-delete', {icon: 'delete'}); - this.selectionDeleteBtn.append('Delete'); + _i18n(this.selectionDeleteBtn, 'Delete'); this.listenerSetter.add(this.selectionDeleteBtn, 'click', () => { new PopupDeleteMessages(this.bubbles.peerId, [...this.selectedMids], this.chat.type, () => { this.cancelSelection(); @@ -461,7 +463,7 @@ export default class ChatSelection { } else { const diff = MAX_SELECTION_LENGTH - this.selectedMids.size - 1; if(diff < 0) { - toast('Max selection count reached.'); + toast(I18n.format('Chat.Selection.LimitToast', true)); return; /* const it = this.selectedMids.values(); do { diff --git a/src/components/dialogsContextMenu.ts b/src/components/dialogsContextMenu.ts index 654df0ac..9e513f35 100644 --- a/src/components/dialogsContextMenu.ts +++ b/src/components/dialogsContextMenu.ts @@ -18,7 +18,7 @@ export default class DialogsContextMenu { private init() { this.buttons = [{ icon: 'unread', - text: 'Mark as unread', + text: 'MarkAsUnread', onClick: this.onUnreadClick, verify: () => { const isUnread = !!(this.dialog.pFlags?.unread_mark || this.dialog.unread_count); @@ -26,7 +26,7 @@ export default class DialogsContextMenu { } }, { icon: 'readchats', - text: 'Mark as read', + text: 'MarkAsRead', onClick: this.onUnreadClick, verify: () => { const isUnread = !!(this.dialog.pFlags?.unread_mark || this.dialog.unread_count); @@ -34,7 +34,7 @@ export default class DialogsContextMenu { } }, { icon: 'pin', - text: 'Pin', + text: 'ChatList.Context.Pin', onClick: this.onPinClick, verify: () => { const isPinned = this.filterId > 1 ? appMessagesManager.filtersStorage.filters[this.filterId].pinned_peers.includes(this.dialog.peerId) : !!this.dialog.pFlags?.pinned; @@ -42,7 +42,7 @@ export default class DialogsContextMenu { } }, { icon: 'unpin', - text: 'Unpin', + text: 'ChatList.Context.Unpin', onClick: this.onPinClick, verify: () => { const isPinned = this.filterId > 1 ? appMessagesManager.filtersStorage.filters[this.filterId].pinned_peers.includes(this.dialog.peerId) : !!this.dialog.pFlags?.pinned; @@ -50,7 +50,7 @@ export default class DialogsContextMenu { } }, { icon: 'mute', - text: 'Mute', + text: 'ChatList.Context.Mute', onClick: this.onMuteClick, verify: () => { const isMuted = this.dialog.notify_settings && this.dialog.notify_settings.mute_until > (Date.now() / 1000 | 0); @@ -58,7 +58,7 @@ export default class DialogsContextMenu { } }, { icon: 'unmute', - text: 'Unmute', + text: 'ChatList.Context.Unmute', onClick: this.onMuteClick, verify: () => { const isMuted = this.dialog.notify_settings && this.dialog.notify_settings.mute_until > (Date.now() / 1000 | 0); diff --git a/src/components/inputField.ts b/src/components/inputField.ts index d264d2d8..e63dfbea 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, _i18n } from "../lib/langPack"; import RichTextProcessor from "../lib/richtextprocessor"; let init = () => { @@ -59,7 +59,7 @@ export enum InputState { }; export type InputFieldOptions = { - placeholder?: string, + placeholder?: LangPackKey, label?: LangPackKey, name?: string, maxLength?: number, @@ -134,11 +134,15 @@ class InputField { } } else { this.container.innerHTML = ` - + `; input = this.container.firstElementChild as HTMLElement; input.addEventListener('input', () => checkAndSetRTL(input)); + + if(placeholder) { + _i18n(input, placeholder, undefined, 'placeholder'); + } } if(label) { diff --git a/src/components/inputSearch.ts b/src/components/inputSearch.ts index fbea8de4..9d39a84a 100644 --- a/src/components/inputSearch.ts +++ b/src/components/inputSearch.ts @@ -1,4 +1,5 @@ //import { getRichValue } from "../helpers/dom"; +import { LangPackKey } from "../lib/langPack"; import InputField from "./inputField"; export default class InputSearch { @@ -12,7 +13,7 @@ export default class InputSearch { public onChange: (value: string) => void; public onClear: () => void; - constructor(placeholder: string, onChange?: (value: string) => void) { + constructor(placeholder: LangPackKey, onChange?: (value: string) => void) { this.inputField = new InputField({ placeholder, plainText: true diff --git a/src/components/misc.ts b/src/components/misc.ts index 8da4f621..a7df6881 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -123,13 +123,13 @@ export function formatPhoneNumber(str: string) { return {formatted: str, country}; } -export function parseMenuButtonsTo(to: {[name: string]: HTMLElement}, elements: HTMLCollection | NodeListOf) { +/* export function parseMenuButtonsTo(to: {[name: string]: HTMLElement}, elements: HTMLCollection | NodeListOf) { Array.from(elements).forEach(el => { const match = el.className.match(/(?:^|\s)menu-(.+?)(?:$|\s)/); if(!match) return; to[match[1]] = el as HTMLElement; }); -} +} */ let onMouseMove = (e: MouseEvent) => { let rect = openedMenu.getBoundingClientRect(); diff --git a/src/components/popups/forward.ts b/src/components/popups/forward.ts index c9e5c374..897610cc 100644 --- a/src/components/popups/forward.ts +++ b/src/components/popups/forward.ts @@ -17,7 +17,7 @@ export default class PopupForward extends PopupPickUser { appImManager.chat.input.initMessagesForward(fromPeerId, mids.slice()); }, onClose, - placeholder: 'Forward to...', + placeholder: 'ShareModal.Search.ForwardPlaceholder', chatRightsAction: 'send_messages' }); } diff --git a/src/components/popups/pickUser.ts b/src/components/popups/pickUser.ts index d35c2a9b..d9fdb3d2 100644 --- a/src/components/popups/pickUser.ts +++ b/src/components/popups/pickUser.ts @@ -1,6 +1,7 @@ import { isTouchSupported } from "../../helpers/touchSupport"; import AppSelectPeers from "../appSelectPeers"; import PopupElement from "."; +import { LangPackKey, _i18n } from "../../lib/langPack"; export default class PopupPickUser extends PopupElement { protected selector: AppSelectPeers; @@ -9,7 +10,7 @@ export default class PopupPickUser extends PopupElement { peerTypes: AppSelectPeers['peerType'], onSelect?: (peerId: number) => Promise | void, onClose?: () => void, - placeholder: string, + placeholder: LangPackKey, chatRightsAction?: AppSelectPeers['chatRightsAction'], peerId?: number, }) { @@ -47,11 +48,11 @@ export default class PopupPickUser extends PopupElement { rippleEnabled: false, avatarSize: 46, peerId: options.peerId, + placeholder: options.placeholder }); //this.scrollable = new Scrollable(this.body); - this.selector.input.placeholder = options.placeholder; this.title.append(this.selector.input); } } diff --git a/src/components/privacySection.ts b/src/components/privacySection.ts index d3a76780..3b46577f 100644 --- a/src/components/privacySection.ts +++ b/src/components/privacySection.ts @@ -117,7 +117,7 @@ export default class PrivacySection { type: 'privacy', skippable: true, title: exception.titleLangKey, - placeholder: 'Add Users or Groups...', + placeholder: 'PrivacyModal.Search.Placeholder', takeOut: (newPeerIds) => { _peerIds.length = 0; _peerIds.push(...newPeerIds); diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index a211dea9..7f61b255 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -8,7 +8,6 @@ import rootScope from "../../lib/rootScope"; import { attachClickEvent, findUpClassName, findUpTag } from "../../helpers/dom"; import { SearchGroup } from "../appSearch"; import "../avatar"; -import { parseMenuButtonsTo } from "../misc"; import Scrollable, { ScrollableX } from "../scrollable"; import InputSearch from "../inputSearch"; import SidebarSlider from "../slider"; @@ -69,8 +68,8 @@ export class AppSidebarLeft extends SidebarSlider { takeOut: (peerIds) => { new AppNewGroupTab(this).open(peerIds); }, - title: 'Add Members', - placeholder: 'Add People...' + title: 'GroupAddMembers', + placeholder: 'SendMessageTo' }); }; @@ -126,23 +125,33 @@ export class AppSidebarLeft extends SidebarSlider { this.menuEl = this.toolsBtn.querySelector('.btn-menu'); this.newBtnMenu = this.sidebarEl.querySelector('#new-menu'); + const _newBtnMenu = ButtonMenu([{ + icon: 'newchannel', + text: 'NewChannel', + onClick: () => { + new AppNewChannelTab(this).open(); + } + }, { + icon: 'newgroup', + text: 'NewGroup', + onClick: onNewGroupClick + }, { + icon: 'newprivate', + text: 'NewPrivateChat', + onClick: onContactsClick + }]); + _newBtnMenu.classList.add('top-left'); + this.newBtnMenu.append(_newBtnMenu); + this.inputSearch.input.addEventListener('focus', () => this.initSearch(), {once: true}); - parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children); + //parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children); this.archivedCount = document.createElement('span'); this.archivedCount.className = 'archived-count badge badge-24 badge-gray'; btnArchive.element.append(this.archivedCount); - attachClickEvent(this.newButtons.privateChat, onContactsClick); - - attachClickEvent(this.newButtons.channel, (e) => { - new AppNewChannelTab(this).open(); - }); - - attachClickEvent(this.newButtons.group, onNewGroupClick); - rootScope.on('dialogs_archived_unread', (e) => { this.archivedCount.innerText = '' + formatNumber(e.count, 1); this.archivedCount.classList.toggle('hide', !e.count); diff --git a/src/components/sidebarLeft/tabs/addMembers.ts b/src/components/sidebarLeft/tabs/addMembers.ts index b436c402..110e020c 100644 --- a/src/components/sidebarLeft/tabs/addMembers.ts +++ b/src/components/sidebarLeft/tabs/addMembers.ts @@ -2,6 +2,7 @@ import { SliderSuperTab } from "../../slider"; import AppSelectPeers from "../../appSelectPeers"; import { putPreloader } from "../../misc"; import Button from "../../button"; +import { LangPackKey, _i18n } from "../../../lib/langPack"; export default class AppAddMembersTab extends SliderSuperTab { private nextBtn: HTMLButtonElement; @@ -40,8 +41,8 @@ export default class AppAddMembersTab extends SliderSuperTab { } public open(options: { - title: string, - placeholder: string, + title: LangPackKey, + placeholder: LangPackKey, peerId?: number, type: AppAddMembersTab['peerType'], takeOut?: AppAddMembersTab['takeOut'], @@ -50,7 +51,7 @@ export default class AppAddMembersTab extends SliderSuperTab { }) { const ret = super.open(); - this.title.innerHTML = options.title; + this.setTitle(options.title); this.peerType = options.type; this.takeOut = options.takeOut; this.skippable = options.skippable; @@ -60,9 +61,9 @@ export default class AppAddMembersTab extends SliderSuperTab { onChange: this.skippable ? null : (length) => { this.nextBtn.classList.toggle('is-visible', !!length); }, - peerType: ['contacts'] + peerType: ['contacts'], + placeholder: options.placeholder }); - this.selector.input.placeholder = options.placeholder; if(options.selectedPeerIds) { this.selector.addInitial(options.selectedPeerIds); diff --git a/src/components/sidebarLeft/tabs/blockedUsers.ts b/src/components/sidebarLeft/tabs/blockedUsers.ts index e818f29a..4529e4e0 100644 --- a/src/components/sidebarLeft/tabs/blockedUsers.ts +++ b/src/components/sidebarLeft/tabs/blockedUsers.ts @@ -31,7 +31,7 @@ export default class AppBlockedUsersTab extends SliderSuperTab { attachClickEvent(btnAdd, (e) => { new PopupPickUser({ peerTypes: ['contacts'], - placeholder: 'Block user...', + placeholder: 'BlockModal.Search.Placeholder', onSelect: (peerId) => { //console.log('block', peerId); appUsersManager.toggleBlock(peerId, true); diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts index c5228ff1..cd8dab3b 100644 --- a/src/components/sidebarLeft/tabs/editFolder.ts +++ b/src/components/sidebarLeft/tabs/editFolder.ts @@ -275,7 +275,7 @@ export default class AppEditFolderTab extends SliderSuperTab { } if(peers.length) { - showMore.innerHTML = `
Show ${Math.min(20, peers.length)} more chat${peers.length > 1 ? 's' : ''}
`; + showMore.lastElementChild.replaceWith(i18n('FilterShowMoreChats', [peers.length])); } else if(showMore) { showMore.remove(); } @@ -286,11 +286,14 @@ export default class AppEditFolderTab extends SliderSuperTab { let showMore: HTMLElement; if(peers.length) { showMore = document.createElement('div'); - showMore.classList.add('show-more'); + showMore.classList.add('show-more', 'rp-overflow'); showMore.addEventListener('click', () => renderMore(20)); - showMore.innerHTML = `
Show ${Math.min(20, peers.length)} more chat${peers.length > 1 ? 's' : ''}
`; ripple(showMore); + const down = document.createElement('div'); + down.classList.add('tgico-down'); + showMore.append(down, i18n('FilterShowMoreChats', [peers.length])); + container.append(showMore); } diff --git a/src/components/sidebarLeft/tabs/includedChats.ts b/src/components/sidebarLeft/tabs/includedChats.ts index 2adaae7a..0b55e521 100644 --- a/src/components/sidebarLeft/tabs/includedChats.ts +++ b/src/components/sidebarLeft/tabs/includedChats.ts @@ -211,10 +211,10 @@ export default class AppIncludedChatsTab extends SliderSuperTab { appendTo: this.container, onChange: this.onSelectChange, peerType: ['dialogs'], - renderResultsFunc: this.renderResults + renderResultsFunc: this.renderResults, + placeholder: 'Search' }); this.selector.selected = new Set(selectedPeers); - this.selector.input.placeholder = 'Search'; const _add = this.selector.add.bind(this.selector); this.selector.add = (peerId, title, scroll) => { diff --git a/src/components/sidebarLeft/tabs/newChannel.ts b/src/components/sidebarLeft/tabs/newChannel.ts index e347665c..ff729e3b 100644 --- a/src/components/sidebarLeft/tabs/newChannel.ts +++ b/src/components/sidebarLeft/tabs/newChannel.ts @@ -6,6 +6,7 @@ import InputField from "../../inputField"; import { SliderSuperTab } from "../../slider"; import AvatarEdit from "../../avatarEdit"; import AppAddMembersTab from "./addMembers"; +import { _i18n } from "../../../lib/langPack"; export default class AppNewChannelTab extends SliderSuperTab { private uploadAvatar: () => Promise = null; @@ -17,7 +18,7 @@ export default class AppNewChannelTab extends SliderSuperTab { protected init() { this.container.classList.add('new-channel-container'); - this.title.innerText = 'New Channel'; + this.setTitle('NewChannel'); this.avatarEdit = new AvatarEdit((_upload) => { this.uploadAvatar = _upload; @@ -27,12 +28,12 @@ export default class AppNewChannelTab extends SliderSuperTab { inputWrapper.classList.add('input-wrapper'); this.channelNameInputField = new InputField({ - label: 'Channel Name', + label: 'Channel.ChannelNameHolder', maxLength: 128 }); this.channelDescriptionInputField = new InputField({ - label: 'Description (optional)', + label: 'Channel.DescriptionPlaceholder', maxLength: 255 }); @@ -49,7 +50,7 @@ export default class AppNewChannelTab extends SliderSuperTab { const caption = document.createElement('div'); caption.classList.add('caption'); - caption.innerText = 'You can provide an optional description for your channel.'; + _i18n(caption, 'Channel.DescriptionHolderDescrpiton'); this.nextBtn = Button('btn-corner btn-circle', {icon: 'arrow_next'}); @@ -70,8 +71,8 @@ export default class AppNewChannelTab extends SliderSuperTab { peerId: channelId, type: 'channel', skippable: true, - title: 'Add Members', - placeholder: 'Add People...', + title: 'GroupAddMembers', + placeholder: 'SendMessageTo', takeOut: (peerIds) => { return appChatsManager.inviteToChannel(Math.abs(channelId), peerIds); } diff --git a/src/components/sidebarLeft/tabs/newGroup.ts b/src/components/sidebarLeft/tabs/newGroup.ts index d20bab9b..caf16b65 100644 --- a/src/components/sidebarLeft/tabs/newGroup.ts +++ b/src/components/sidebarLeft/tabs/newGroup.ts @@ -8,6 +8,7 @@ import Button from "../../button"; import InputField from "../../inputField"; import { SliderSuperTab } from "../../slider"; import AvatarEdit from "../../avatarEdit"; +import { i18n } from "../../../lib/langPack"; export default class AppNewGroupTab extends SliderSuperTab { private searchGroup = new SearchGroup(' ', 'contacts', true, 'new-group-members disable-hover', false); @@ -19,7 +20,7 @@ export default class AppNewGroupTab extends SliderSuperTab { protected init() { this.container.classList.add('new-group-container'); - this.title.innerText = 'New Group'; + this.setTitle('NewGroup'); this.avatarEdit = new AvatarEdit((_upload) => { this.uploadAvatar = _upload; @@ -29,7 +30,7 @@ export default class AppNewGroupTab extends SliderSuperTab { inputWrapper.classList.add('input-wrapper'); this.groupNameInputField = new InputField({ - label: 'Group Name', + label: 'CreateGroup.NameHolder', maxLength: 128 }); @@ -99,7 +100,8 @@ export default class AppNewGroupTab extends SliderSuperTab { } }); - this.searchGroup.nameEl.innerText = this.userIds.length + ' members'; + this.searchGroup.nameEl.textContent = ''; + this.searchGroup.nameEl.append(i18n('Members', [this.userIds.length])); this.searchGroup.setActive(); }); diff --git a/src/components/sidebarRight/tabs/groupPermissions.ts b/src/components/sidebarRight/tabs/groupPermissions.ts index b32a2770..a9b9967a 100644 --- a/src/components/sidebarRight/tabs/groupPermissions.ts +++ b/src/components/sidebarRight/tabs/groupPermissions.ts @@ -154,7 +154,7 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable { openPermissions(peerId); }, 0); }, - placeholder: 'Add Exception...', + placeholder: 'ExceptionModal.Search.Placeholder', peerId: -this.chatId, }); } diff --git a/src/config/app.ts b/src/config/app.ts index a009b0c6..846cb5f6 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -2,7 +2,7 @@ const App = { id: 1025907, hash: '452b0359b988148995f22ff0f4229750', version: '0.4.0', - langPackVersion: '0.0.2', + langPackVersion: '0.0.3', domains: [] as string[], baseDcId: 2 }; diff --git a/src/index.hbs b/src/index.hbs index 9f2a406c..e5f71697 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -117,13 +117,7 @@ - + diff --git a/src/lang.ts b/src/lang.ts index b73e59ac..adfeb06d 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -1,4 +1,6 @@ const lang = { + "AttachAlbum": "Album", + "BlockModal.Search.Placeholder": "Block user...", "FilterIncludeExcludeInfo": "Choose chats and types of chats that will\nappear and never appear in this folder.", "FilterNameInputLabel": "Folder Name", "FilterMenuDelete": "Delete Folder", @@ -20,7 +22,13 @@ const lang = { "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.", + "ExceptionModal.Search.Placeholder": "Add exception...", "ChatList.Menu.Archived": "Archived", + "Chat.Selection.MessagesCount": { + "one_value": "%d Message", + "other_value": "%d Messages", + }, + "Chat.Selection.LimitToast": "Max selection count reached.", "Saved": "Saved", "General.Keyboard": "Keyboard", "General.SendShortcut.Enter": "Send by Enter", @@ -33,12 +41,19 @@ const lang = { "ChatBackground.Blur": "Blur Wallpaper Image", "Notifications.Sound": "Notification Sound", "Notifications.MessagePreview": "Message preview", + "NewPrivateChat": "New Private Chat", + "Message.Context.Selection.Copy": "Copy selected", + "Message.Context.Selection.Clear": "Clear selection", + "Message.Context.Selection.Delete": "Delete selected", + "Message.Context.Selection.Forward": "Forward selected", + "Message.Context.Selection.SendNow": "Send Now selected", "Checkbox.Enabled": "Enabled", "Checkbox.Disabled": "Disabled", "Privacy.Devices": { "one_value": "%1$d device", "other_value": "%1$d devices" }, + "PrivacyModal.Search.Placeholder": "Add Users or Groups...", // * android "ActionCreateChannel": "Channel created", @@ -56,8 +71,21 @@ const lang = { "ActionInviteUser": "un1 joined the group via invite link", "ActionPinnedNoText": "un1 pinned a message", "ActionMigrateFromGroup": "This group was upgraded to a supergroup", + "AttachPhoto": "Photo", + "AttachVideo": "Video", + "AttachGif": "GIF", + "AttachLocation": "Location", + "AttachLiveLocation": "Live Location", + "AttachContact": "Contact", + "AttachDocument": "File", + "AttachSticker": "Sticker", + "AttachAudio": "Voice message", + "AttachRound": "Video message", + "AttachGame": "Game", //"ChannelJoined": "You joined this channel", "ChannelMegaJoined": "You joined this group", + "Channel.DescriptionPlaceholder": "Description (optional)", + "Draft": "Draft", "FilterAlwaysShow": "Include Chats", "FilterNeverShow": "Exclude Chats", "FilterInclude": "Included Chats", @@ -67,6 +95,11 @@ const lang = { "FilterNew": "New Folder", "Filters": "Folders", "FilterRecommended": "Recommended Folders", + "FilterShowMoreChats": { + "one_value": "Show %1$d More Chat", + "other_value": "Show %1$d More Chats" + }, + "FromYou": "You", "Add": "Add", "Chats": { "one_value": "%1$d chat", @@ -84,7 +117,12 @@ const lang = { "one_value": "%1$d user", "other_value": "%1$d users" }, + "Members": { + "one_value": "%1$d member", + "other_value": "%1$d members" + }, "UsernameHelpLink": "This link opens a chat with you:\n%1$s", + "NewChannel": "New Channel", "NewGroup": "New Group", "Contacts": "Contacts", "SavedMessages": "Saved Messages", @@ -108,6 +146,8 @@ const lang = { "NotificationsGroups": "Groups", "NotificationsChannels": "Channels", "NotificationsOther": "Other", + "MarkAsUnread": "Mark as unread", + "MarkAsRead": "Mark as read", "ContactJoined": "Contact joined Telegram", "Loading": "Loading...", "Unblock": "Unblock", @@ -143,6 +183,20 @@ const lang = { "ArchivedChats": "Archived Chats", "Cancel": "Cancel", "HistoryCleared": "History was cleared", + "Archive": "Archive", + "Unarchive": "Unarchive", + "Delete": "Delete", + "Reply": "Reply", + "Edit": "Edit", + "Forward": "Forward", + "CopyLink": "Copy Link", + "Copy": "Copy", + "Search": "Search", + "LinkCopied": "Link copied to clipboard", + "LinkCopiedPrivateInfo": "This link will only work for members of this chat.", + "GroupAddMembers": "Add Members", + "SendMessageTo": "Add people...", + "SelectChat": "Select Chat", // * macos "AccountSettings.Filters": "Chat Folders", @@ -150,6 +204,7 @@ const lang = { "AccountSettings.PrivacyAndSecurity": "Privacy and Security", "AccountSettings.Language": "Language", "Bio.Description": "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco", + "Chat.CopySelectedText": "Copy Selected Text", "Chat.Date.ScheduledFor": "Scheduled for %@", //"Chat.Date.ScheduledUntilOnline": "Scheduled until online", "Chat.Date.ScheduledForToday": "Scheduled for today", @@ -159,6 +214,14 @@ const lang = { "Chat.Service.Channel.RemovedPhoto": "Channel photo removed", "Chat.Service.Channel.UpdatedVideo": "Channel video updated", "Chat.Service.BotPermissionAllowed": "You allowed this bot to message you when you logged in on %@", + "Chat.Poll.Unvote": "Retract Vote", + "Chat.Poll.Stop": "Stop Poll", + "Chat.Poll.Stop.Confirm.Header": "Stop Poll?", + "Chat.Poll.Stop.Confirm.Text": "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.", + "ChatList.Context.Mute": "Mute", + "ChatList.Context.Unmute": "Unmute", + "ChatList.Context.Pin": "Pin", + "ChatList.Context.Unpin": "Unpin", "ChatList.Service.Call.incoming": "Incoming Call (%@)", "ChatList.Service.Call.outgoing": "Outgoing Call (%@)", "ChatList.Service.Call.Cancelled": "Cancelled Call", @@ -177,6 +240,9 @@ const lang = { "ChatList.Filter.MutedChats": "Muted", "ChatList.Filter.ReadChats": "Read", "ChatList.Filter.Archive": "Archived", + "Channel.ChannelNameHolder": "Channel Name", + "Channel.DescriptionHolderDescrpiton": "You can provide an optional description for your channel.", + "CreateGroup.NameHolder": "Group Name", "Date.Today": "Today", "EditAccount.Username": "Username", "EditAccount.Title": "Edit Profile", @@ -186,6 +252,7 @@ const lang = { "Telegram.InstalledStickerPacksController": "Stickers", "Telegram.NotificationSettingsViewController": "Notifications", "Stickers.SuggestStickers": "Suggest Stickers by Emoji", + "ShareModal.Search.ForwardPlaceholder": "Forward to...", "InstalledStickers.LoopAnimated": "Loop Animated Stickers", "PrivacyAndSecurity.Item.On": "On", "PrivacyAndSecurity.Item.Off": "Off", @@ -211,7 +278,10 @@ const lang = { "one_value": "%d user", "other_value": "%d users" }, - "RecentSessions.Error.FreshReset": "For security reasons, you can't terminate older sessions from a device that you've just connected. Please use an earlier connection or wait for a few hours." + "RcentSessions.Error.FreshReset": "For security reasons, you can't terminate older sessions from a device that you've just connected. Please use an earlier connection or wait for a few hours.", + "Message.Context.Select": "Select", + "Message.Context.Pin": "Pin", + "Message.Context.Unpin": "Unpin", }; export default lang; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index ba82e0b8..1a169a4e 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -29,6 +29,7 @@ import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; import appNotificationsManager from "./appNotificationsManager"; import { InputNotifyPeer } from "../../layer"; import PeerTitle from "../../components/peerTitle"; +import { i18n } from "../langPack"; type DialogDom = { avatarEl: AvatarElement, @@ -1069,7 +1070,8 @@ export class AppDialogsManager { if(draftMessage) { const bold = document.createElement('b'); bold.classList.add('danger'); - bold.innerHTML = 'Draft: '; + bold.append(i18n('Draft')); + bold.append(': '); dom.lastMessageSpan.prepend(bold); } else if(peer._ !== 'peerUser' && peerId !== lastMessage.fromId && !lastMessage.action) { const sender = appPeersManager.getPeer(lastMessage.fromId); @@ -1077,7 +1079,7 @@ export class AppDialogsManager { const senderBold = document.createElement('b'); if(sender.id === rootScope.myId) { - senderBold.innerHTML = 'You'; + senderBold.append(i18n('FromYou')); } else { //str = sender.first_name || sender.last_name || sender.username; senderBold.append(new PeerTitle({ diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 19b017c5..a0d32cf5 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -8,7 +8,7 @@ import { randomLong } from "../../helpers/random"; import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string"; import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update } from "../../layer"; import { InvokeApiOptions } from "../../types"; -import I18n, { join, langPack, LangPackKey, _i18n } from "../langPack"; +import I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack"; import { logger, LogLevels } from "../logger"; import type { ApiFileManager } from '../mtproto/apiFileManager'; //import apiManager from '../mtproto/apiManager'; @@ -2505,10 +2505,6 @@ export class AppMessagesManager { const parts: (HTMLElement | string)[] = []; const addPart = (part: string | HTMLElement, text?: string) => { - if(text) { - part += ', '; - } - if(plain) { parts.push(part); } else { @@ -2517,6 +2513,10 @@ export class AppMessagesManager { else el.append(part); parts.push(el); } + + if(text) { + parts.push(', '); + } }; if(message.media) { @@ -2538,7 +2538,7 @@ export class AppMessagesManager { if(usingFullAlbum) { text = this.getAlbumText(message.grouped_id).message; - addPart('Album', text); + addPart(i18n('AttachAlbum'), text); } } else { usingFullAlbum = false; @@ -2548,31 +2548,31 @@ export class AppMessagesManager { const media = message.media; switch(media._) { case 'messageMediaPhoto': - addPart('Photo', message.message); + addPart(i18n('AttachPhoto'), message.message); break; case 'messageMediaDice': addPart(plain ? media.emoticon : RichTextProcessor.wrapEmojiText(media.emoticon)); break; case 'messageMediaGeo': - addPart('Geolocation'); + addPart(i18n('AttachLiveLocation')); break; case 'messageMediaPoll': addPart(plain ? '📊' + ' ' + (media.poll.question || 'poll') : media.poll.rReply); break; case 'messageMediaContact': - addPart('Contact'); + addPart(i18n('AttachContact')); break; case 'messageMediaDocument': let document = media.document; if(document.type === 'video') { - addPart('Video', message.message); + addPart(i18n('AttachVideo'), message.message); } else if(document.type === 'voice') { - addPart('Voice message', message.message); + addPart(i18n('AttachAudio'), message.message); } else if(document.type === 'gif') { - addPart('GIF', message.message); + addPart(i18n('AttachGif'), message.message); } else if(document.type === 'round') { - addPart('Video message', message.message); + addPart(i18n('AttachRound'), message.message); } else if(document.type === 'sticker') { addPart(((plain ? document.stickerEmojiRaw : document.stickerEmoji) || '') + 'Sticker'); text = ''; diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index 69629b3d..a264c35e 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -459,6 +459,12 @@ } } +.new-group-container { + .search-group { + margin-top: .5rem; + } +} + .edit-folder-container { .folder-category-button:nth-child(n+2) { pointer-events: none;