From ad9a6918d7dcd28c7ed31b824fbb329c077806a0 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Wed, 24 Mar 2021 21:45:38 +0400 Subject: [PATCH] Better langpack formatting Date formatting Fix datepicker closening Fix jumping by forwarded --- src/components/chat/bubbles.ts | 44 ++- src/components/popups/datePicker.ts | 3 +- .../sidebarLeft/tabs/activeSessions.ts | 2 +- .../sidebarLeft/tabs/editProfile.ts | 8 +- src/helpers/date.ts | 26 ++ src/helpers/string.ts | 4 + src/lang.ts | 264 +++++++++--------- src/lib/appManagers/appDialogsManager.ts | 9 +- src/lib/appManagers/appMessagesManager.ts | 24 +- src/lib/langPack.ts | 138 ++++++--- src/scripts/format_lang.js | 37 +++ src/scripts/out/langPack.strings | 190 +++++++++++++ src/scss/partials/_chatBubble.scss | 4 +- 13 files changed, 553 insertions(+), 200 deletions(-) create mode 100644 src/scripts/format_lang.js create mode 100644 src/scripts/out/langPack.strings diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 47559b04..6cdac6c3 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -26,7 +26,7 @@ import { months } from "../../helpers/date"; import RichTextProcessor from "../../lib/richtextprocessor"; import mediaSizes from "../../helpers/mediaSizes"; import { isAndroid, isApple, isSafari } from "../../helpers/userAgent"; -import { langPack } from "../../lib/langPack"; +import I18n, { i18n, langPack } from "../../lib/langPack"; import AvatarElement from "../avatar"; import { formatPhoneNumber } from "../misc"; import { ripple } from "../ripple"; @@ -716,7 +716,7 @@ export default class ChatBubbles { } //this.log('chatInner click:', target); - const isVideoComponentElement = target.tagName === 'SPAN'; + const isVideoComponentElement = target.tagName === 'SPAN' && !target.classList.contains('peer-title'); /* if(isVideoComponentElement) { const video = target.parentElement.querySelector('video') as HTMLElement; if(video) { @@ -808,9 +808,9 @@ export default class ChatBubbles { return; } - if(['IMG', 'DIV', "AVATAR-ELEMENT"/* , 'A' */].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); + if(['IMG', 'DIV', "AVATAR-ELEMENT", 'SPAN'/* , 'A' */].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); - if(target.tagName === 'DIV' || target.tagName === "AVATAR-ELEMENT"/* || target.tagName === 'A' */) { + if(['DIV', 'SPAN'].indexOf(target.tagName) !== -1/* || target.tagName === 'A' */) { if(target.classList.contains('goto-original')) { const savedFrom = bubble.dataset.savedFrom; const splitted = savedFrom.split('_'); @@ -824,7 +824,7 @@ export default class ChatBubbles { new PopupForward(this.peerId, [mid]); //appSidebarRight.forwardTab.open([mid]); return; - } else if(target.classList.contains('name')) { + } else if(target.classList.contains('peer-title') || target.classList.contains('name')) { const peerId = +target.dataset.peerId; const savedFrom = target.dataset.savedFrom; @@ -1251,28 +1251,48 @@ export default class ChatBubbles { date.setHours(0, 0, 0); const dateTimestamp = date.getTime(); if(!(dateTimestamp in this.dateMessages)) { - let str = ''; + let dateElement: HTMLElement; const today = new Date(); today.setHours(0, 0, 0, 0); if(today.getTime() === date.getTime()) { - str = 'Today'; + dateElement = i18n(this.chat.type === 'scheduled' ? 'Chat.Date.ScheduledForToday' : 'Date.Today'); } else { - str = months[date.getMonth()] + ' ' + date.getDate(); + const options: Intl.DateTimeFormatOptions = { + day: 'numeric', + month: 'long' + }; if(date.getFullYear() !== today.getFullYear()) { - str += ', ' + date.getFullYear(); + options.year = 'numeric'; + } + + dateElement = new I18n.IntlDateElement({ + date, + options + }).element; + + if(this.chat.type === 'scheduled') { + dateElement = i18n('Chat.Date.ScheduledFor', [dateElement]); } } - if(this.chat.type === 'scheduled') { + /* if(this.chat.type === 'scheduled') { str = 'Scheduled for ' + str; - } + } */ const div = document.createElement('div'); div.className = 'bubble service is-date'; - div.innerHTML = `
${str}
`; + const bubbleContent = document.createElement('div'); + bubbleContent.classList.add('bubble-content'); + const serviceMsg = document.createElement('div'); + serviceMsg.classList.add('service-msg'); + + serviceMsg.append(dateElement); + + bubbleContent.append(serviceMsg); + div.append(bubbleContent); ////////this.log('need to render date message', dateTimestamp, str); const container = document.createElement('div'); diff --git a/src/components/popups/datePicker.ts b/src/components/popups/datePicker.ts index 71033d18..03c3da4b 100644 --- a/src/components/popups/datePicker.ts +++ b/src/components/popups/datePicker.ts @@ -222,6 +222,8 @@ export default class PopupDatePicker extends PopupElement { if(this.selectedEl === target) return; this.selectedEl.classList.remove('active'); } + + this.selectedEl = target; target.classList.add('active'); const timestamp = +target.dataset.timestamp; @@ -229,7 +231,6 @@ export default class PopupDatePicker extends PopupElement { this.selectedDate = new Date(timestamp); this.setTitle(); - this.setMonth(); this.setTimeTitle(); }; diff --git a/src/components/sidebarLeft/tabs/activeSessions.ts b/src/components/sidebarLeft/tabs/activeSessions.ts index 3364b329..28b7c6fb 100644 --- a/src/components/sidebarLeft/tabs/activeSessions.ts +++ b/src/components/sidebarLeft/tabs/activeSessions.ts @@ -98,7 +98,7 @@ export default class AppActiveSessionsTab extends SliderSuperTab { const onError = (err: any) => { if(err.type === 'FRESH_RESET_AUTHORISATION_FORBIDDEN') { - toast(I18n.getString('RecentSessions.Error.FreshReset')); + toast(I18n.format('RecentSessions.Error.FreshReset', true)); } }; diff --git a/src/components/sidebarLeft/tabs/editProfile.ts b/src/components/sidebarLeft/tabs/editProfile.ts index e89a0301..9c1edc59 100644 --- a/src/components/sidebarLeft/tabs/editProfile.ts +++ b/src/components/sidebarLeft/tabs/editProfile.ts @@ -98,20 +98,16 @@ export default class AppEditProfileTab extends SliderSuperTab { const profileUrlContainer = this.profileUrlContainer = document.createElement('div'); profileUrlContainer.classList.add('profile-url-container'); - profileUrlContainer.append(i18n('UsernameHelpLink', [''])); - + const profileUrlAnchor = this.profileUrlAnchor = document.createElement('a'); profileUrlAnchor.classList.add('profile-url'); profileUrlAnchor.href = '#'; profileUrlAnchor.target = '_blank'; - profileUrlContainer.append(profileUrlAnchor); + profileUrlContainer.append(i18n('UsernameHelpLink', [profileUrlAnchor])); caption.append(profileUrlContainer); - this.profileUrlContainer = caption.querySelector('.profile-url-container'); - this.profileUrlAnchor = this.profileUrlContainer.lastElementChild as HTMLAnchorElement; - inputFields.push(this.usernameInputField); this.scrollable.append(h2, inputWrapper, caption); } diff --git a/src/helpers/date.ts b/src/helpers/date.ts index 34f7b0e6..9d644a4d 100644 --- a/src/helpers/date.ts +++ b/src/helpers/date.ts @@ -1,4 +1,5 @@ import { MOUNT_CLASS_TO } from "../config/debug"; +import I18n from "../lib/langPack"; export const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; export const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; @@ -33,6 +34,31 @@ export const formatDateAccordingToToday = (time: Date) => { return timeStr; }; +export function formatDateAccordingToTodayNew(time: Date) { + const today = new Date(); + const now = today.getTime() / 1000 | 0; + const timestamp = time.getTime() / 1000 | 0; + + const options: Intl.DateTimeFormatOptions = {}; + if((now - timestamp) < ONE_DAY && today.getDate() === time.getDate()) { // if the same day + options.hour = options.minute = '2-digit'; + options.hour12 = false; + } else if(today.getFullYear() !== time.getFullYear()) { // different year + options.year = options.day = 'numeric'; + options.month = '2-digit'; + } else if((now - timestamp) < (ONE_DAY * 7) && getWeekNumber(today) === getWeekNumber(time)) { // current week + options.weekday = 'short'; + } else { // same year + options.month = 'short'; + options.day = 'numeric'; + } + + return new I18n.IntlDateElement({ + date: time, + options + }).element; +} + export const getFullDate = (date: Date, options: Partial<{ noTime: true, noSeconds: true, diff --git a/src/helpers/string.ts b/src/helpers/string.ts index 9387756f..873653e2 100644 --- a/src/helpers/string.ts +++ b/src/helpers/string.ts @@ -98,3 +98,7 @@ export function convertKeyToInputKey(key: string) { key = 'input' + key; return key; } + +export function capitalizeFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} diff --git a/src/lang.ts b/src/lang.ts index 68fef30b..5bc99088 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -1,27 +1,27 @@ const lang = { - FilterIncludeExcludeInfo: 'Choose chats and types of chats that will\nappear and never appear in this folder.', - FilterNameInputLabel: 'Folder Name', - FilterMenuDelete: 'Delete Folder', - FilterHeaderEdit: 'Edit Folder', - FilterAllGroups: 'All Groups', - FilterAllContacts: 'All Contacts', - FilterAllNonContacts: 'All Non-Contacts', - FilterAllChannels: 'All Channels', - FilterAllBots: 'All Bots', - FilterAllUnmuted: 'All Unmuted', - FilterAllUnread: 'All Unread', - FilterAllUnarchived: 'All Unarchived', - WordDelimiter: ', ', - WordDelimiterLast: ' and ', - "EditProfile.FirstNameLabel": 'Name', - "EditProfile.BioLabel": 'Bio (optional)', - "EditProfile.Username.Label": 'Username (optional)', - "EditProfile.Username.Available": 'Username is available', - "EditProfile.Username.Taken": 'Username is already taken', - "EditProfile.Username.Invalid": 'Username is invalid', + "FilterIncludeExcludeInfo": "Choose chats and types of chats that will\nappear and never appear in this folder.", + "FilterNameInputLabel": "Folder Name", + "FilterMenuDelete": "Delete Folder", + "FilterHeaderEdit": "Edit Folder", + "FilterAllGroups": "All Groups", + "FilterAllContacts": "All Contacts", + "FilterAllNonContacts": "All Non-Contacts", + "FilterAllChannels": "All Channels", + "FilterAllBots": "All Bots", + "FilterAllUnmuted": "All Unmuted", + "FilterAllUnread": "All Unread", + "FilterAllUnarchived": "All Unarchived", + "WordDelimiter": ", ", + "WordDelimiterLast": " and ", + "EditProfile.FirstNameLabel": "Name", + "EditProfile.BioLabel": "Bio (optional)", + "EditProfile.Username.Label": "Username (optional)", + "EditProfile.Username.Available": "Username is available", + "EditProfile.Username.Taken": "Username is already taken", + "EditProfile.Username.Invalid": "Username is invalid", "EditProfile.Username.Help": "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\nYou can use a–z, 0–9 and underscores. Minimum length is 5 characters.", "ChatList.Menu.Archived": "Archived", - Saved: "Saved", + "Saved": "Saved", "General.Keyboard": "Keyboard", "General.SendShortcut.Enter": "Send by Enter", "General.SendShortcut.CtrlEnter": "Send by %s + Enter", @@ -29,120 +29,128 @@ const lang = { "General.SendShortcut.NewLine.Enter": "New line by Enter", "General.AutoplayMedia": "Auto-Play Media", "ChatBackground.UploadWallpaper": "Upload Wallpaper", - "ChatBackground.SetColor": "Upload Wallpaper", - "ChatBackground.Blur": "Upload Wallpaper", + "ChatBackground.SetColor": "Set a Color", + "ChatBackground.Blur": "Blur Wallpaper Image", "Notifications.Sound": "Notification Sound", "Notifications.MessagePreview": "Message preview", "Checkbox.Enabled": "Enabled", "Checkbox.Disabled": "Disabled", "Privacy.Devices": { - one_value: '%1$d device', - other_value: '%1$d devices' + "one_value": "%1$d device", + "other_value": "%1$d devices" }, // * android - ActionCreateChannel: "Channel created", - ActionCreateGroup: "un1 created the group", - ActionChangedTitle: "un1 changed the group name to un2", - ActionRemovedPhoto: "un1 removed the group photo", - ActionChangedPhoto: "un1 changed the group photo", - ActionChangedVideo: "un1 changed the group video", - ActionAddUser: "un1 added un2", - ActionAddUserSelf: "un1 returned to the group", - ActionAddUserSelfMega: "un1 joined the group", - ActionAddUserSelfYou: "You returned to the group", - ActionLeftUser: "un1 left the group", - ActionKickUser: "un1 removed un2", - ActionInviteUser: "un1 joined the group via invite link", - ActionPinnedNoText: "un1 pinned a message", - ActionMigrateFromGroup: "This group was upgraded to a supergroup", - FilterAlwaysShow: 'Include Chats', - FilterNeverShow: 'Exclude Chats', - FilterInclude: 'Included Chats', - FilterExclude: 'Excluded Chats', - FilterChatTypes: 'Chat types', - FilterChats: 'Chats', - FilterNew: 'New Folder', - Filters: 'Folders', - FilterRecommended: 'Recommended Folders', - Add: 'Add', - Chats: { - one_value: '%1$d chat', - other_value: '%1$d chats' + "ActionCreateChannel": "Channel created", + "ActionCreateGroup": "un1 created the group", + "ActionChangedTitle": "un1 changed the group name to un2", + "ActionRemovedPhoto": "un1 removed the group photo", + "ActionChangedPhoto": "un1 changed the group photo", + "ActionChangedVideo": "un1 changed the group video", + "ActionAddUser": "un1 added un2", + "ActionAddUserSelf": "un1 returned to the group", + "ActionAddUserSelfMega": "un1 joined the group", + "ActionAddUserSelfYou": "You returned to the group", + "ActionLeftUser": "un1 left the group", + "ActionKickUser": "un1 removed un2", + "ActionInviteUser": "un1 joined the group via invite link", + "ActionPinnedNoText": "un1 pinned a message", + "ActionMigrateFromGroup": "This group was upgraded to a supergroup", + "FilterAlwaysShow": "Include Chats", + "FilterNeverShow": "Exclude Chats", + "FilterInclude": "Included Chats", + "FilterExclude": "Excluded Chats", + "FilterChatTypes": "Chat types", + "FilterChats": "Chats", + "FilterNew": "New Folder", + "Filters": "Folders", + "FilterRecommended": "Recommended Folders", + "Add": "Add", + "Chats": { + "one_value": "%1$d chat", + "other_value": "%1$d chats" }, - Channels: { - one_value: '%1$d channel', - other_value: '%1$d channels' + "Channels": { + "one_value": "%1$d channel", + "other_value": "%1$d channels" }, - Groups: { - one_value: '%1$d group', - other_value: '%1$d groups' + "Groups": { + "one_value": "%1$d group", + "other_value": "%1$d groups" }, - Users: { - one_value: "%1$d user", - other_value: "%1$d users" + "Users": { + "one_value": "%1$d user", + "other_value": "%1$d users" }, - UsernameHelpLink: "This link opens a chat with you:\n%1$s", - NewGroup: "New Group", - Contacts: "Contacts", - SavedMessages: "Saved Messages", - Settings: "Settings", - SettingsHelp: "Help", - General: "General", - TextSize: "Message Text Size", - ChatBackground: "Chat Background", - EnableAnimations: "Enable Animations", - AutoDownloadMedia: "Auto-Download Media", - AutodownloadContacts: 'Contacts', - AutodownloadPrivateChats: 'Private Chats', - AutodownloadGroupChats: 'Group Chats', - AutodownloadChannels: 'Channels', - AutoplayGIF: 'GIFs', - AutoplayVideo: 'Videos', - NotificationsForGroups: 'Notifications for groups', - NotificationsForPrivateChats: 'Notifications for private chats', - NotificationsForChannels: 'Notifications for channels', - NotificationsPrivateChats: "Private Chats", - NotificationsGroups: "Groups", - NotificationsChannels: "Channels", - NotificationsOther: 'Other', - ContactJoined: 'Contact joined Telegram', - Loading: "Loading...", - Unblock: "Unblock", - BlockedUsers: "Blocked Users", - BlockedUsersInfo: 'Blocked users will not be able to contact you and will not see your Last Seen time.', - BlockedEmpty: "None", - TwoStepVerification: "Two-Step Verification", - PrivacyExceptions: "Exceptions", - PrivacyLastSeen: "Last Seen & Online", - PrivacySettings: "Privacy and Security", - PrivacyTitle: "Privacy", - PrivacyPhone: "Phone Number", - PrivacyPhoneTitle: "Who can see my phone number?", - PrivacyPhoneTitle2: "Who can find me by my number?", - PrivacyPhoneInfo: "Users who have your number saved in their contacts will also see it on Telegram.", - PrivacyPhoneInfo3: "Users who add your number to their contacts will see it on Telegram only if they are your contacts.", - PrivacyProfilePhoto: "Profile Photos", - PrivacyProfilePhotoTitle: "Who can see my profile photos & videos?", - PrivacyP2PHeader: "Peer-to-Peer", - PrivacyForwardsTitle: "Who can add a link to my account when forwarding my messages?", - LastSeenTitle: "Who can see your Last Seen time?", - SessionsTitle: "Active Sessions", - CurrentSession: "This device", - TerminateAllSessions: "Terminate All Other Sessions", - TerminateSessionText: "Are you sure you want to terminate this session?", - OtherSessions: "Active sessions", - AreYouSureSessionTitle: "Terminate session", - AreYouSureSessionsTitle: "Terminate sessions", - AreYouSureSessions: "Are you sure you want to terminate all other sessions?", - Terminate: "Terminate", - WhoCanCallMe: "Who can call me?", - WhoCanAddMe: "Who can add me to group chats?", - ArchivedChats: "Archived Chats", - Cancel: "Cancel", - HistoryCleared: "History was cleared", + "UsernameHelpLink": "This link opens a chat with you:\n%1$s", + "NewGroup": "New Group", + "Contacts": "Contacts", + "SavedMessages": "Saved Messages", + "Settings": "Settings", + "SettingsHelp": "Help", + "General": "General", + "TextSize": "Message Text Size", + "ChatBackground": "Chat Background", + "EnableAnimations": "Enable Animations", + "AutoDownloadMedia": "Auto-Download Media", + "AutodownloadContacts": "Contacts", + "AutodownloadPrivateChats": "Private Chats", + "AutodownloadGroupChats": "Group Chats", + "AutodownloadChannels": "Channels", + "AutoplayGIF": "GIFs", + "AutoplayVideo": "Videos", + "NotificationsForGroups": "Notifications for groups", + "NotificationsForPrivateChats": "Notifications for private chats", + "NotificationsForChannels": "Notifications for channels", + "NotificationsPrivateChats": "Private Chats", + "NotificationsGroups": "Groups", + "NotificationsChannels": "Channels", + "NotificationsOther": "Other", + "ContactJoined": "Contact joined Telegram", + "Loading": "Loading...", + "Unblock": "Unblock", + "BlockedUsers": "Blocked Users", + "BlockedUsersInfo": "Blocked users will not be able to contact you and will not see your Last Seen time.", + "BlockedEmpty": "None", + "TwoStepVerification": "Two-Step Verification", + "PrivacyExceptions": "Exceptions", + "PrivacyLastSeen": "Last Seen & Online", + "PrivacySettings": "Privacy and Security", + "PrivacyTitle": "Privacy", + "PrivacyPhone": "Phone Number", + "PrivacyPhoneTitle": "Who can see my phone number?", + "PrivacyPhoneTitle2": "Who can find me by my number?", + "PrivacyPhoneInfo": "Users who have your number saved in their contacts will also see it on Telegram.", + "PrivacyPhoneInfo3": "Users who add your number to their contacts will see it on Telegram only if they are your contacts.", + "PrivacyProfilePhoto": "Profile Photos", + "PrivacyProfilePhotoTitle": "Who can see my profile photos & videos?", + "PrivacyP2PHeader": "Peer-to-Peer", + "PrivacyForwardsTitle": "Who can add a link to my account when forwarding my messages?", + "LastSeenTitle": "Who can see your Last Seen time?", + "SessionsTitle": "Active Sessions", + "CurrentSession": "This device", + "TerminateAllSessions": "Terminate All Other Sessions", + "TerminateSessionText": "Are you sure you want to terminate this session?", + "OtherSessions": "Active sessions", + "AreYouSureSessionTitle": "Terminate session", + "AreYouSureSessionsTitle": "Terminate sessions", + "AreYouSureSessions": "Are you sure you want to terminate all other sessions?", + "Terminate": "Terminate", + "WhoCanCallMe": "Who can call me?", + "WhoCanAddMe": "Who can add me to group chats?", + "ArchivedChats": "Archived Chats", + "Cancel": "Cancel", + "HistoryCleared": "History was cleared", // * macos + "AccountSettings.Filters": "Chat Folders", + "AccountSettings.Notifications": "Notifications and Sounds", + "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.Date.ScheduledFor": "Scheduled for %@", + //"Chat.Date.ScheduledUntilOnline": "Scheduled until online", + "Chat.Date.ScheduledForToday": "Scheduled for today", "Chat.Service.PeerJoinedTelegram": "%@ joined Telegram", "Chat.Service.Channel.UpdatedTitle": "Channel renamed to \"%@\"", "Chat.Service.Channel.UpdatedPhoto": "Channel photo updated", @@ -166,15 +174,11 @@ const lang = { "ChatList.Filter.MutedChats": "Muted", "ChatList.Filter.ReadChats": "Read", "ChatList.Filter.Archive": "Archived", - "Bio.Description": "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco", + "Date.Today": "Today", "EditAccount.Username": "Username", "EditAccount.Title": "Edit Profile", "EditAccount.Logout": "Log Out", "Login.Register.LastName.Placeholder": "Last Name", - "AccountSettings.Filters": "Chat Folders", - "AccountSettings.Notifications": "Notifications and Sounds", - "AccountSettings.PrivacyAndSecurity": "Privacy and Security", - "AccountSettings.Language": "Language", "Telegram.GeneralSettingsViewController": "General Settings", "Telegram.InstalledStickerPacksController": "Stickers", "Telegram.NotificationSettingsViewController": "Notifications", @@ -191,7 +195,7 @@ const lang = { "PrivacySettingsController.P2p.Desc": "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but may slightly decrease audio and video quality.", "PrivacySettingsController.PhoneCallDescription": "You can restrict who can call you with granular precision.", "PrivacySettingsController.ProfilePhoto.CustomHelp": "You can restrict who can see your profile photo with granular precision.", - "PrivacySettingsController.LastSeenDescription": "You won't see Last Seen and Online statuses for people with whom you don't share yours. Approximate last seen will be shown instead (recently, within a week, within a month).", + "PrivacySettingsController.LastSeenDescription": "You won\"t see Last Seen and Online statuses for people with whom you don\"t share yours. Approximate last seen will be shown instead (recently, within a week, within a month).", "PrivacySettingsController.PeerInfo": "You can add users or entire groups as exceptions that will override the settings above.", "PrivacySettingsController.Everbody": "Everybody", "PrivacySettingsController.MyContacts": "My Contacts", @@ -201,10 +205,10 @@ const lang = { "PrivacySettingsController.NeverAllow": "Never Allow", "PrivacySettingsController.AlwaysAllow": "Always Allow", "PrivacySettingsController.UserCount": { - one_value: '%d user', - other_value: '%d users' + "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.", + "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." }; export default lang; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index d0417056..d845afdd 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -5,13 +5,13 @@ import { attachContextMenuListener, putPreloader } from "../../components/misc"; import { ripple } from "../../components/ripple"; //import Scrollable from "../../components/scrollable"; import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable"; -import { formatDateAccordingToToday } from "../../helpers/date"; +import { formatDateAccordingToTodayNew } from "../../helpers/date"; import { escapeRegExp } from "../../helpers/string"; import { isSafari } from "../../helpers/userAgent"; import { logger, LogLevels } from "../logger"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; -import { findUpClassName, findUpTag, positionElementByIndex } from "../../helpers/dom"; +import { findUpTag, positionElementByIndex } from "../../helpers/dom"; import appImManager from "./appImManager"; import appMessagesManager, { Dialog } from "./appMessagesManager"; import {MyDialogFilter as DialogFilter} from "../storages/filters"; @@ -1120,8 +1120,9 @@ export class AppDialogsManager { if(!lastMessage.deleted || draftMessage/* && lastMessage._ !== 'draftMessage' */) { const date = draftMessage ? Math.max(draftMessage.date, lastMessage.date || 0) : lastMessage.date; - dom.lastTimeSpan.innerHTML = formatDateAccordingToToday(new Date(date * 1000)); - } else dom.lastTimeSpan.innerHTML = ''; + dom.lastTimeSpan.textContent = ''; + dom.lastTimeSpan.append(formatDateAccordingToTodayNew(new Date(date * 1000))); + } else dom.lastTimeSpan.textContent = ''; if(this.doms[peerId] === dom) { this.setUnreadMessages(dialog); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index b69254f1..b3a86435 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -38,6 +38,7 @@ import appProfileManager from "./appProfileManager"; import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray"; import appNotificationsManager, { NotifyOptions } from "./appNotificationsManager"; +import PeerTitle from "../../components/peerTitle"; //console.trace('include'); // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет @@ -2687,6 +2688,8 @@ export class AppMessagesManager { const element: HTMLElement = plain ? undefined : document.createElement('span'); const action = message.action as MessageAction; + // this.log('message action:', action); + if((action as MessageAction.messageActionCustomAction).message) { const richText = RichTextProcessor.wrapRichText((action as MessageAction.messageActionCustomAction).message, {noLinebreaks: true}); if(plain) { @@ -2701,9 +2704,8 @@ export class AppMessagesManager { let langPackKey: LangPackKey = ''; let args: any[]; - const getNameDivHTML = (peerId: number) => { - const title = appPeersManager.getPeerTitle(peerId); - return title ? (plain ? title + ' ' : `
${title}
`) : ''; + const getNameDivHTML = (peerId: number, plain: boolean) => { + return plain ? appPeersManager.getPeerTitle(peerId) + ' ' : (new PeerTitle({peerId})).element; }; switch(action._) { @@ -2722,9 +2724,10 @@ export class AppMessagesManager { break; } + case 'messageActionChatCreate': case 'messageActionChatJoinedByLink': { langPackKey = langPack[_]; - args = [getNameDivHTML(message.fromId)]; + args = [getNameDivHTML(message.fromId, plain)]; break; } @@ -2736,7 +2739,12 @@ export class AppMessagesManager { || [(action as MessageAction.messageActionChatDeleteUser).user_id]; langPackKey = langPack[_]; - args = [getNameDivHTML(message.fromId), users.map((userId: number) => getNameDivHTML(userId).trim()).join(', ')]; + args = [ + getNameDivHTML(message.fromId, plain), + users.length > 1 ? + users.map((userId: number) => (getNameDivHTML(userId, true) as string).trim()).join(', ') : + getNameDivHTML(users[0], plain) + ]; break; } @@ -2767,17 +2775,13 @@ export class AppMessagesManager { } if(plain) { - return I18n.getString(langPackKey, args); + return I18n.format(langPackKey, true, args); } else { return _i18n(element, langPackKey, args); } //str = !langPackKey || langPackKey[0].toUpperCase() === langPackKey[0] ? langPackKey : getNameDivHTML(message.fromId) + langPackKey + (suffix ? ' ' : ''); } - - //this.log('message action:', action); - - return element; } public editPeerFolders(peerIds: number[], folderId: number) { diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 5f8a68be..2373a821 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -1,5 +1,6 @@ import { MOUNT_CLASS_TO } from "../config/debug"; import { safeAssign } from "../helpers/object"; +import { capitalizeFirstLetter } from "../helpers/string"; import type lang from "../lang"; import { LangPackDifference, LangPackString } from "../layer"; import apiManager from "./mtproto/mtprotoworker"; @@ -140,69 +141,138 @@ namespace I18n { } }); } + + export function superFormatter(input: string, args?: any[], indexHolder = {i: 0}) { + let out: (string | HTMLElement)[] = []; + const regExp = /(\*\*)(.+?)\1|(\n)|un\d|%\d\$.|%./g; + + let lastIndex = 0; + input.replace(regExp, (match, p1: any, p2: any, p3: any, offset: number, string: string) => { + //console.table({match, p1, p2, offset, string}); + + out.push(string.slice(lastIndex, offset)); + + if(p1) { + //offset += p1.length; + switch(p1) { + case '**': { + const b = document.createElement('b'); + b.append(...superFormatter(p2, args, indexHolder)); + out.push(b); + break; + } + } + } else if(p3) { + out.push(document.createElement('br')); + } else if(args) { + out.push(args[indexHolder.i++]); + } + + lastIndex = offset + match.length; + return ''; + }); - export function getString(key: LangPackKey, args?: any[]) { - const str = strings.get(key); - let out = ''; + if(lastIndex !== (input.length - 1)) { + out.push(input.slice(lastIndex)); + } + return out; + } + + export function format(key: LangPackKey, plain: true, args?: any[]): string; + export function format(key: LangPackKey, plain?: false, args?: any[]): (string | HTMLElement)[]; + export function format(key: LangPackKey, plain = false, args?: any[]): (string | HTMLElement)[] | string { + const str = strings.get(key); + let input: string; if(str) { if(str._ === 'langPackStringPluralized' && args?.length) { const v = args[0] as number; const s = pluralRules.select(v); // @ts-ignore - out = str[s + '_value'] || str['other_value']; + input = str[s + '_value'] || str['other_value']; } else if(str._ === 'langPackString') { - out = str.value; + input = str.value; } else { - out = '[' + key + ']'; - //out = key; + input = '[' + key + ']'; + //input = key; } } else { - out = '[' + key + ']'; - //out = key; + input = '[' + key + ']'; + //input = key; } + + if(plain) { + if(args?.length) { + const regExp = /un\d|%\d\$.|%./g; + let i = 0; + input = input.replace(regExp, (match, offset, string) => { + return '' + args[i++]; + }); + } - out = out - .replace(/\n/g, '
') - .replace(/\*\*(.+?)\*\*/g, '$1'); - - if(args?.length) { - let i = 0; - out = out.replace(/un\d|%\d\$.|%./g, (match, offset, string) => { - return '' + args[i++]; - }); + return input; + } else { + return superFormatter(input, args); } - - return out; } - export const weakMap: WeakMap = new WeakMap(); + export const weakMap: WeakMap> = new WeakMap(); - export type IntlElementOptions = { + export type IntlElementBaseOptions = { element?: HTMLElement, - property?: /* 'innerText' | */'innerHTML' | 'placeholder' - key: LangPackKey, - args?: any[] + property?: /* 'innerText' | */'innerHTML' | 'placeholder', }; - export class IntlElement { - public element: IntlElementOptions['element']; - public key: IntlElementOptions['key']; - public args: IntlElementOptions['args']; - public property: IntlElementOptions['property'] = 'innerHTML'; + + abstract class IntlElementBase { + public element: IntlElementBaseOptions['element']; + public property: IntlElementBaseOptions['property'] = 'innerHTML'; - constructor(options: IntlElementOptions) { + constructor(options: Options) { this.element = options.element || document.createElement('span'); this.element.classList.add('i18n'); this.update(options); weakMap.set(this.element, this); } - + + abstract update(options?: Options): void; + } + + export type IntlElementOptions = IntlElementBaseOptions & { + key: LangPackKey, + args?: any[] + }; + export class IntlElement extends IntlElementBase { + public key: IntlElementOptions['key']; + public args: IntlElementOptions['args']; + public update(options?: IntlElementOptions) { safeAssign(this, options); - const str = getString(this.key, this.args); - (this.element as any)[this.property] = str; + if(this.property === 'innerHTML') { + this.element.textContent = ''; + this.element.append(...format(this.key, false, this.args)); + } else { + (this.element as HTMLInputElement)[this.property] = format(this.key, true, this.args); + } + } + } + + export type IntlDateElementOptions = IntlElementBaseOptions & { + date: Date, + options: Intl.DateTimeFormatOptions + }; + export class IntlDateElement extends IntlElementBase { + public date: IntlDateElementOptions['date']; + public options: IntlDateElementOptions['options']; + + public update(options?: IntlDateElementOptions) { + safeAssign(this, options); + + //var options = { month: 'long', day: 'numeric' }; + const dateTimeFormat = new Intl.DateTimeFormat(lastRequestedLangCode, this.options); + + (this.element as any)[this.property] = capitalizeFirstLetter(dateTimeFormat.format(this.date)); } } diff --git a/src/scripts/format_lang.js b/src/scripts/format_lang.js new file mode 100644 index 00000000..74b3cacf --- /dev/null +++ b/src/scripts/format_lang.js @@ -0,0 +1,37 @@ +const fs = require('fs'); + +let str = fs.readFileSync('../lang.ts').toString().replace(/\s.+\/\/.+/g, ''); +{ + const pattern = '= {'; + str = str.slice(str.indexOf(pattern) + pattern.length - 1); +} + +{ + const pattern = '};'; + str = str.slice(0, str.indexOf(pattern) + pattern.length - 1); +} + +//console.log(str); +const json = JSON.parse(str); +//console.log(json); + +const f = (key, value, plural) => { + value = value + .replace(/\n/g, '\\n') + .replace(/"/g, '\\"'); + return `"${key}${plural ? '_' + plural.replace('_value', '') : ''}" = "${value}";\n`; +}; + +let out = ''; +for(const key in json) { + const value = json[key]; + if(typeof(value) === 'string') { + out += f(key, value); + } else { + for(const plural in value) { + out += f(key, value[plural], plural); + } + } +} + +fs.writeFileSync('./out/langPack.strings', out); diff --git a/src/scripts/out/langPack.strings b/src/scripts/out/langPack.strings new file mode 100644 index 00000000..6fc9f369 --- /dev/null +++ b/src/scripts/out/langPack.strings @@ -0,0 +1,190 @@ +"FilterIncludeExcludeInfo" = "Choose chats and types of chats that will\nappear and never appear in this folder."; +"FilterNameInputLabel" = "Folder Name"; +"FilterMenuDelete" = "Delete Folder"; +"FilterHeaderEdit" = "Edit Folder"; +"FilterAllGroups" = "All Groups"; +"FilterAllContacts" = "All Contacts"; +"FilterAllNonContacts" = "All Non-Contacts"; +"FilterAllChannels" = "All Channels"; +"FilterAllBots" = "All Bots"; +"FilterAllUnmuted" = "All Unmuted"; +"FilterAllUnread" = "All Unread"; +"FilterAllUnarchived" = "All Unarchived"; +"WordDelimiter" = ", "; +"WordDelimiterLast" = " and "; +"EditProfile.FirstNameLabel" = "Name"; +"EditProfile.BioLabel" = "Bio (optional)"; +"EditProfile.Username.Label" = "Username (optional)"; +"EditProfile.Username.Available" = "Username is available"; +"EditProfile.Username.Taken" = "Username is already taken"; +"EditProfile.Username.Invalid" = "Username is invalid"; +"EditProfile.Username.Help" = "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\nYou can use a–z, 0–9 and underscores. Minimum length is 5 characters."; +"ChatList.Menu.Archived" = "Archived"; +"Saved" = "Saved"; +"General.Keyboard" = "Keyboard"; +"General.SendShortcut.Enter" = "Send by Enter"; +"General.SendShortcut.CtrlEnter" = "Send by %s + Enter"; +"General.SendShortcut.NewLine.ShiftEnter" = "New line by Shift + Enter"; +"General.SendShortcut.NewLine.Enter" = "New line by Enter"; +"General.AutoplayMedia" = "Auto-Play Media"; +"ChatBackground.UploadWallpaper" = "Upload Wallpaper"; +"ChatBackground.SetColor" = "Upload Wallpaper"; +"ChatBackground.Blur" = "Upload Wallpaper"; +"Notifications.Sound" = "Notification Sound"; +"Notifications.MessagePreview" = "Message preview"; +"Checkbox.Enabled" = "Enabled"; +"Checkbox.Disabled" = "Disabled"; +"Privacy.Devices_one" = "%1$d device"; +"Privacy.Devices_other" = "%1$d devices"; +"ActionCreateChannel" = "Channel created"; +"ActionCreateGroup" = "un1 created the group"; +"ActionChangedTitle" = "un1 changed the group name to un2"; +"ActionRemovedPhoto" = "un1 removed the group photo"; +"ActionChangedPhoto" = "un1 changed the group photo"; +"ActionChangedVideo" = "un1 changed the group video"; +"ActionAddUser" = "un1 added un2"; +"ActionAddUserSelf" = "un1 returned to the group"; +"ActionAddUserSelfMega" = "un1 joined the group"; +"ActionAddUserSelfYou" = "You returned to the group"; +"ActionLeftUser" = "un1 left the group"; +"ActionKickUser" = "un1 removed un2"; +"ActionInviteUser" = "un1 joined the group via invite link"; +"ActionPinnedNoText" = "un1 pinned a message"; +"ActionMigrateFromGroup" = "This group was upgraded to a supergroup"; +"FilterAlwaysShow" = "Include Chats"; +"FilterNeverShow" = "Exclude Chats"; +"FilterInclude" = "Included Chats"; +"FilterExclude" = "Excluded Chats"; +"FilterChatTypes" = "Chat types"; +"FilterChats" = "Chats"; +"FilterNew" = "New Folder"; +"Filters" = "Folders"; +"FilterRecommended" = "Recommended Folders"; +"Add" = "Add"; +"Chats_one" = "%1$d chat"; +"Chats_other" = "%1$d chats"; +"Channels_one" = "%1$d channel"; +"Channels_other" = "%1$d channels"; +"Groups_one" = "%1$d group"; +"Groups_other" = "%1$d groups"; +"Users_one" = "%1$d user"; +"Users_other" = "%1$d users"; +"UsernameHelpLink" = "This link opens a chat with you:\n%1$s"; +"NewGroup" = "New Group"; +"Contacts" = "Contacts"; +"SavedMessages" = "Saved Messages"; +"Settings" = "Settings"; +"SettingsHelp" = "Help"; +"General" = "General"; +"TextSize" = "Message Text Size"; +"ChatBackground" = "Chat Background"; +"EnableAnimations" = "Enable Animations"; +"AutoDownloadMedia" = "Auto-Download Media"; +"AutodownloadContacts" = "Contacts"; +"AutodownloadPrivateChats" = "Private Chats"; +"AutodownloadGroupChats" = "Group Chats"; +"AutodownloadChannels" = "Channels"; +"AutoplayGIF" = "GIFs"; +"AutoplayVideo" = "Videos"; +"NotificationsForGroups" = "Notifications for groups"; +"NotificationsForPrivateChats" = "Notifications for private chats"; +"NotificationsForChannels" = "Notifications for channels"; +"NotificationsPrivateChats" = "Private Chats"; +"NotificationsGroups" = "Groups"; +"NotificationsChannels" = "Channels"; +"NotificationsOther" = "Other"; +"ContactJoined" = "Contact joined Telegram"; +"Loading" = "Loading..."; +"Unblock" = "Unblock"; +"BlockedUsers" = "Blocked Users"; +"BlockedUsersInfo" = "Blocked users will not be able to contact you and will not see your Last Seen time."; +"BlockedEmpty" = "None"; +"TwoStepVerification" = "Two-Step Verification"; +"PrivacyExceptions" = "Exceptions"; +"PrivacyLastSeen" = "Last Seen & Online"; +"PrivacySettings" = "Privacy and Security"; +"PrivacyTitle" = "Privacy"; +"PrivacyPhone" = "Phone Number"; +"PrivacyPhoneTitle" = "Who can see my phone number?"; +"PrivacyPhoneTitle2" = "Who can find me by my number?"; +"PrivacyPhoneInfo" = "Users who have your number saved in their contacts will also see it on Telegram."; +"PrivacyPhoneInfo3" = "Users who add your number to their contacts will see it on Telegram only if they are your contacts."; +"PrivacyProfilePhoto" = "Profile Photos"; +"PrivacyProfilePhotoTitle" = "Who can see my profile photos & videos?"; +"PrivacyP2PHeader" = "Peer-to-Peer"; +"PrivacyForwardsTitle" = "Who can add a link to my account when forwarding my messages?"; +"LastSeenTitle" = "Who can see your Last Seen time?"; +"SessionsTitle" = "Active Sessions"; +"CurrentSession" = "This device"; +"TerminateAllSessions" = "Terminate All Other Sessions"; +"TerminateSessionText" = "Are you sure you want to terminate this session?"; +"OtherSessions" = "Active sessions"; +"AreYouSureSessionTitle" = "Terminate session"; +"AreYouSureSessionsTitle" = "Terminate sessions"; +"AreYouSureSessions" = "Are you sure you want to terminate all other sessions?"; +"Terminate" = "Terminate"; +"WhoCanCallMe" = "Who can call me?"; +"WhoCanAddMe" = "Who can add me to group chats?"; +"ArchivedChats" = "Archived Chats"; +"Cancel" = "Cancel"; +"HistoryCleared" = "History was cleared"; +"Chat.Service.PeerJoinedTelegram" = "%@ joined Telegram"; +"Chat.Service.Channel.UpdatedTitle" = "Channel renamed to \"%@\""; +"Chat.Service.Channel.UpdatedPhoto" = "Channel photo updated"; +"Chat.Service.Channel.RemovedPhoto" = "Channel photo removed"; +"Chat.Service.BotPermissionAllowed" = "You allowed this bot to message you when you logged in on %@"; +"ChatList.Service.Call.incoming" = "Incoming Call (%@)"; +"ChatList.Service.Call.outgoing" = "Outgoing Call (%@)"; +"ChatList.Service.Call.Cancelled" = "Cancelled Call"; +"ChatList.Service.Call.Missed" = "Missed Call"; +"ChatList.Filter.Header" = "Create folders for different groups of chats and quickly switch between them."; +"ChatList.Filter.NewTitle" = "Create Folder"; +"ChatList.Filter.List.Title" = "Chat Folders"; +"ChatList.Filter.Include.AddChat" = "Add Chats"; +"ChatList.Filter.Exclude.AddChat" = "Add Chats"; +"ChatList.Filter.All" = "All"; +"ChatList.Filter.Contacts" = "Contacts"; +"ChatList.Filter.NonContacts" = "Non-Contacts"; +"ChatList.Filter.Groups" = "Groups"; +"ChatList.Filter.Channels" = "Channels"; +"ChatList.Filter.Bots" = "Bots"; +"ChatList.Filter.MutedChats" = "Muted"; +"ChatList.Filter.ReadChats" = "Read"; +"ChatList.Filter.Archive" = "Archived"; +"Bio.Description" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco"; +"EditAccount.Username" = "Username"; +"EditAccount.Title" = "Edit Profile"; +"EditAccount.Logout" = "Log Out"; +"Login.Register.LastName.Placeholder" = "Last Name"; +"AccountSettings.Filters" = "Chat Folders"; +"AccountSettings.Notifications" = "Notifications and Sounds"; +"AccountSettings.PrivacyAndSecurity" = "Privacy and Security"; +"AccountSettings.Language" = "Language"; +"Telegram.GeneralSettingsViewController" = "General Settings"; +"Telegram.InstalledStickerPacksController" = "Stickers"; +"Telegram.NotificationSettingsViewController" = "Notifications"; +"Stickers.SuggestStickers" = "Suggest Stickers by Emoji"; +"InstalledStickers.LoopAnimated" = "Loop Animated Stickers"; +"PrivacyAndSecurity.Item.On" = "On"; +"PrivacyAndSecurity.Item.Off" = "Off"; +"PrivacySettings.VoiceCalls" = "Calls"; +"PrivacySettings.Forwards" = "Forwarded Messages"; +"PrivacySettings.Groups" = "Groups and Channels"; +"PrivacySettingsController.AddUsers" = "Add Users"; +"PrivacySettingsController.GroupDescription" = "You can restrict who can add you to groups and channels with granular precision."; +"PrivacySettingsController.Forwards.CustomHelp" = "You can restrict who can add a link to your account when forwarding your messages."; +"PrivacySettingsController.P2p.Desc" = "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but may slightly decrease audio and video quality."; +"PrivacySettingsController.PhoneCallDescription" = "You can restrict who can call you with granular precision."; +"PrivacySettingsController.ProfilePhoto.CustomHelp" = "You can restrict who can see your profile photo with granular precision."; +"PrivacySettingsController.LastSeenDescription" = "You won\"t see Last Seen and Online statuses for people with whom you don\"t share yours. Approximate last seen will be shown instead (recently, within a week, within a month)."; +"PrivacySettingsController.PeerInfo" = "You can add users or entire groups as exceptions that will override the settings above."; +"PrivacySettingsController.Everbody" = "Everybody"; +"PrivacySettingsController.MyContacts" = "My Contacts"; +"PrivacySettingsController.Nobody" = "Nobody"; +"PrivacySettingsController.NeverShare" = "Never Share With"; +"PrivacySettingsController.AlwaysShare" = "Always Share With"; +"PrivacySettingsController.NeverAllow" = "Never Allow"; +"PrivacySettingsController.AlwaysAllow" = "Always Allow"; +"PrivacySettingsController.UserCount_one" = "%d user"; +"PrivacySettingsController.UserCount_other" = "%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."; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index ccf232c5..3210af22 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -1572,13 +1572,13 @@ $bubble-margin: .25rem; color: #fff; } - a, .name { + a, .peer-title { &:hover { text-decoration: underline; } } - .name { + .peer-title { cursor: pointer; //margin-right: 5px; }