diff --git a/app/common/link-util.ts b/app/common/link-util.ts index 43ab0de6..6054fa50 100644 --- a/app/common/link-util.ts +++ b/app/common/link-util.ts @@ -3,7 +3,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import {html} from "./html.ts"; +import {Html, html} from "./html.ts"; +import * as t from "./translation-util.ts"; export async function openBrowser(url: URL): Promise { if (["http:", "https:", "mailto:"].includes(url.protocol)) { @@ -21,7 +22,7 @@ export async function openBrowser(url: URL): Promise { - Redirecting + ${t.__("Redirecting")} -

Opening ${url.href}

+

+ ${new Html({ + html: t.__("Opening {{{link}}}…", { + link: html`${url.href}`.html, + }), + })} +

`.html, diff --git a/app/common/messages.ts b/app/common/messages.ts index 545b0991..585f440e 100644 --- a/app/common/messages.ts +++ b/app/common/messages.ts @@ -1,3 +1,5 @@ +import * as t from "./translation-util.ts"; + type DialogBoxError = { title: string; content: string; @@ -13,26 +15,24 @@ export function invalidZulipServerError(domain: string): string { https://zulip.readthedocs.io/en/stable/production/ssl-certificates.html`; } -export function enterpriseOrgError( - length: number, - domains: string[], -): DialogBoxError { +export function enterpriseOrgError(domains: string[]): DialogBoxError { let domainList = ""; for (const domain of domains) { domainList += `• ${domain}\n`; } return { - title: `Could not add the following ${ - length === 1 ? "organization" : "organizations" - }`, - content: `${domainList}\nPlease contact your system administrator.`, + title: t.__mf( + "{number, plural, one {Could not add # organization} other {Could not add # organizations}}", + {number: domains.length}, + ), + content: `${domainList}\n${t.__("Please contact your system administrator.")}`, }; } export function orgRemovalError(url: string): DialogBoxError { return { - title: `Removing ${url} is a restricted operation.`, - content: "Please contact your system administrator.", + title: t.__("Removing {{{url}}} is a restricted operation.", {url}), + content: t.__("Please contact your system administrator."), }; } diff --git a/app/common/translation-util.ts b/app/common/translation-util.ts index 71feb672..b818a88d 100644 --- a/app/common/translation-util.ts +++ b/app/common/translation-util.ts @@ -13,4 +13,4 @@ i18n.configure({ /* Fetches the current appLocale from settings.json */ i18n.setLocale(ConfigUtil.getConfigItem("appLanguage", "en") ?? "en"); -export {__} from "i18n"; +export {__, __mf} from "i18n"; diff --git a/app/main/handle-external-link.ts b/app/main/handle-external-link.ts index d44dcbc8..c5fe4828 100644 --- a/app/main/handle-external-link.ts +++ b/app/main/handle-external-link.ts @@ -11,6 +11,7 @@ import path from "node:path"; import * as ConfigUtil from "../common/config-util.ts"; import * as LinkUtil from "../common/link-util.ts"; +import * as t from "../common/translation-util.ts"; import {send} from "./typed-ipc-main.ts"; @@ -125,8 +126,8 @@ export default function handleExternalLink( downloadPath, async completed(filePath: string, fileName: string) { const downloadNotification = new Notification({ - title: "Download Complete", - body: `Click to show ${fileName} in folder`, + title: t.__("Download Complete"), + body: t.__("Click to show {{{fileName}}} in folder", {fileName}), silent: true, // We'll play our own sound - ding.ogg }); downloadNotification.on("click", () => { @@ -149,8 +150,8 @@ export default function handleExternalLink( if (state !== "cancelled") { if (ConfigUtil.getConfigItem("promptDownload", false)) { new Notification({ - title: "Download Complete", - body: "Download failed", + title: t.__("Download Complete"), + body: t.__("Download failed"), }).show(); } else { contents.downloadURL(url.href); diff --git a/app/main/linuxupdater.ts b/app/main/linuxupdater.ts index 955aef8f..cc3b7a28 100644 --- a/app/main/linuxupdater.ts +++ b/app/main/linuxupdater.ts @@ -5,6 +5,7 @@ import {z} from "zod"; import * as ConfigUtil from "../common/config-util.ts"; import Logger from "../common/logger-util.ts"; +import * as t from "../common/translation-util.ts"; import * as LinuxUpdateUtil from "./linux-update-util.ts"; @@ -34,8 +35,11 @@ export async function linuxUpdateNotification(session: Session): Promise { const notified = LinuxUpdateUtil.getUpdateItem(latestVersion); if (notified === null) { new Notification({ - title: "Zulip Update", - body: `A new version ${latestVersion} is available. Please update using your package manager.`, + title: t.__("Zulip Update"), + body: t.__( + "A new version {{{version}}} is available. Please update using your package manager.", + {version: latestVersion}, + ), }).show(); LinuxUpdateUtil.setUpdateItem(latestVersion, true); } diff --git a/app/renderer/js/main.ts b/app/renderer/js/main.ts index d3d7d96b..fbed215e 100644 --- a/app/renderer/js/main.ts +++ b/app/renderer/js/main.ts @@ -80,7 +80,6 @@ export class ServerManagerView { $dndTooltip: HTMLElement; $sidebar: Element; $fullscreenPopup: Element; - $fullscreenEscapeKey: string; loading: Set; activeTabIndex: number; tabs: ServerOrFunctionalTab[]; @@ -121,8 +120,10 @@ export class ServerManagerView { this.$sidebar = document.querySelector("#sidebar")!; this.$fullscreenPopup = document.querySelector("#fullscreen-popup")!; - this.$fullscreenEscapeKey = process.platform === "darwin" ? "^⌘F" : "F11"; - this.$fullscreenPopup.textContent = `Press ${this.$fullscreenEscapeKey} to exit full screen`; + this.$fullscreenPopup.textContent = t.__( + "Press {{{exitKey}}} to exit full screen", + {exitKey: process.platform === "darwin" ? "^⌘F" : "F11"}, + ); this.loading = new Set(); this.activeTabIndex = -1; @@ -261,7 +262,10 @@ export class ServerManagerView { } catch (error: unknown) { logger.error(error); logger.error( - `Could not add ${domain}. Please contact your system administrator.`, + t.__( + "Could not add {{{domain}}}. Please contact your system administrator.", + {domain}, + ), ); return false; } @@ -311,10 +315,7 @@ export class ServerManagerView { failedDomains.push(org); } - const {title, content} = Messages.enterpriseOrgError( - domainsAdded.length, - failedDomains, - ); + const {title, content} = Messages.enterpriseOrgError(failedDomains); dialog.showErrorBox(title, content); if (DomainUtil.getDomains().length === 0) { // No orgs present, stop showing loading gif @@ -795,8 +796,9 @@ export class ServerManagerView { // Toggles the dnd button icon. toggleDndButton(alert: boolean): void { - this.$dndTooltip.textContent = - (alert ? "Disable" : "Enable") + " Do Not Disturb"; + this.$dndTooltip.textContent = alert + ? t.__("Disable Do Not Disturb") + : t.__("Enable Do Not Disturb"); const $dndIcon = this.$dndButton.querySelector("i")!; $dndIcon.textContent = alert ? "notifications_off" : "notifications"; diff --git a/app/renderer/js/pages/preference/base-section.ts b/app/renderer/js/pages/preference/base-section.ts index 7bb954c6..dcd8cf16 100644 --- a/app/renderer/js/pages/preference/base-section.ts +++ b/app/renderer/js/pages/preference/base-section.ts @@ -1,4 +1,5 @@ import {type Html, html} from "../../../../common/html.ts"; +import * as t from "../../../../common/translation-util.ts"; import {generateNodeFromHtml} from "../../components/base.ts"; import {ipcRenderer} from "../../typed-ipc-renderer.ts"; @@ -31,7 +32,7 @@ export function generateOptionHtml( const labelHtml = disabled ? html`` : html``; if (settingOption) { diff --git a/app/renderer/js/pages/preference/general-section.ts b/app/renderer/js/pages/preference/general-section.ts index d2f7b696..b981e0c4 100644 --- a/app/renderer/js/pages/preference/general-section.ts +++ b/app/renderer/js/pages/preference/general-section.ts @@ -561,8 +561,9 @@ export function initGeneralSection({$root}: GeneralSectionProperties): void { } async function factoryResetSettings(): Promise { - const clearAppDataMessage = - "When the application restarts, it will be as if you have just downloaded Zulip app."; + const clearAppDataMessage = t.__( + "When the application restarts, it will be as if you have just downloaded the Zulip app.", + ); const getAppPath = path.join(app.getPath("appData"), app.name); const {response} = await dialog.showMessageBox({ @@ -609,7 +610,7 @@ export function initGeneralSection({$root}: GeneralSectionProperties): void { spellDiv.innerHTML += html`
${t.__("Spellchecker Languages")}
- +
`.html; diff --git a/app/renderer/js/pages/preference/network-section.ts b/app/renderer/js/pages/preference/network-section.ts index 39213cca..70bd1453 100644 --- a/app/renderer/js/pages/preference/network-section.ts +++ b/app/renderer/js/pages/preference/network-section.ts @@ -28,7 +28,7 @@ export function initNetworkSection({$root}: NetworkSectionProperties): void {
- PAC ${t.__("script")} + ${t.__("PAC script")}
@@ -60,12 +62,12 @@ export function initNewServerForm({ )!; async function submitFormHandler(): Promise { - $saveServerButton.textContent = "Connecting..."; + $saveServerButton.textContent = t.__("Connecting…"); let serverConfig; try { serverConfig = await DomainUtil.checkDomain($newServerUrl.value.trim()); } catch (error: unknown) { - $saveServerButton.textContent = "Connect"; + $saveServerButton.textContent = t.__("Connect"); await dialog.showMessageBox({ type: "error", message: diff --git a/app/renderer/js/tray.ts b/app/renderer/js/tray.ts index 356f11ed..96273945 100644 --- a/app/renderer/js/tray.ts +++ b/app/renderer/js/tray.ts @@ -7,6 +7,7 @@ import {BrowserWindow, Menu, Tray} from "@electron/remote"; import * as ConfigUtil from "../../common/config-util.ts"; import {publicPath} from "../../common/paths.ts"; +import * as t from "../../common/translation-util.ts"; import type {RendererMessage} from "../../common/typed-ipc.ts"; import type {ServerManagerView} from "./main.ts"; @@ -147,13 +148,13 @@ function sendAction( const createTray = function (): void { const contextMenu = Menu.buildFromTemplate([ { - label: "Zulip", + label: t.__("Zulip"), click() { ipcRenderer.send("focus-app"); }, }, { - label: "Settings", + label: t.__("Settings"), click() { ipcRenderer.send("focus-app"); sendAction("open-settings"); @@ -163,7 +164,7 @@ const createTray = function (): void { type: "separator", }, { - label: "Quit", + label: t.__("Quit"), click() { ipcRenderer.send("quit-app"); }, @@ -202,12 +203,17 @@ export function initializeTray(serverManagerView: ServerManagerView) { if (argument === 0) { unread = argument; tray.setImage(iconPath()); - tray.setToolTip("No unread messages"); + tray.setToolTip(t.__("No unread messages")); } else { unread = argument; const image = renderNativeImage(argument); tray.setImage(image); - tray.setToolTip(`${argument} unread messages`); + tray.setToolTip( + t.__mf( + "{number, plural, one {# unread message} other {# unread messages}}", + {number: `${argument}`}, + ), + ); } } }); diff --git a/app/renderer/js/utils/reconnect-util.ts b/app/renderer/js/utils/reconnect-util.ts index 8af7892a..7acaaf97 100644 --- a/app/renderer/js/utils/reconnect-util.ts +++ b/app/renderer/js/utils/reconnect-util.ts @@ -2,6 +2,7 @@ import * as backoff from "backoff"; import {html} from "../../../common/html.ts"; import Logger from "../../../common/logger-util.ts"; +import * as t from "../../../common/translation-util.ts"; import type WebView from "../components/webview.ts"; import {ipcRenderer} from "../typed-ipc-renderer.ts"; @@ -55,8 +56,10 @@ export default class ReconnectUtil { const errorMessageHolder = document.querySelector("#description"); if (errorMessageHolder) { errorMessageHolder.innerHTML = html` -
Your internet connection doesn't seem to work properly!
-
Verify that it works and then click try again.
+
+ ${t.__("Your internet connection doesn't seem to work properly!")} +
+
${t.__("Verify that it works and then click Reconnect.")}
`.html; } diff --git a/i18next-parser.config.ts b/i18next-parser.config.ts index 0c0821cb..504551d7 100644 --- a/i18next-parser.config.ts +++ b/i18next-parser.config.ts @@ -8,7 +8,7 @@ const config: UserConfig = { input: ["app/**/*.ts"], keySeparator: false, lexers: { - ts: [{lexer: "JavascriptLexer", functions: ["t.__"]}], + ts: [{lexer: "JavascriptLexer", functions: ["t.__", "t.__mf"]}], }, locales: ["en"], namespaceSeparator: false, diff --git a/public/translations/en.json b/public/translations/en.json index b46f2f36..d1ca4bf1 100644 --- a/public/translations/en.json +++ b/public/translations/en.json @@ -1,5 +1,6 @@ { "A new update {{{version}}} has been downloaded.": "A new update {{{version}}} has been downloaded.", + "A new version {{{version}}} is available. Please update using your package manager.": "A new version {{{version}}} is available. Please update using your package manager.", "A new version {{{version}}} of Zulip Desktop is available.": "A new version {{{version}}} of Zulip Desktop is available.", "About": "About", "About Zulip": "About Zulip", @@ -29,16 +30,19 @@ "Change": "Change", "Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.", "Check for Updates": "Check for Updates", + "Click to show {{{fileName}}} in folder": "Click to show {{{fileName}}} in folder", "Close": "Close", "Connect": "Connect", "Connect to another organization": "Connect to another organization", "Connected organizations": "Connected organizations", + "Connecting…": "Connecting…", "Copy": "Copy", "Copy Email Address": "Copy Email Address", "Copy Image": "Copy Image", "Copy Image URL": "Copy Image URL", "Copy Link": "Copy Link", "Copy Zulip URL": "Copy Zulip URL", + "Could not add {{{domain}}}. Please contact your system administrator.": "Could not add {{{domain}}}. Please contact your system administrator.", "Create a new organization": "Create a new organization", "Custom CSS file deleted": "Custom CSS file deleted", "Cut": "Cut", @@ -46,17 +50,22 @@ "Delete": "Delete", "Desktop Notifications": "Desktop Notifications", "Desktop Settings": "Desktop Settings", + "Disable Do Not Disturb": "Disable Do Not Disturb", "Disconnect": "Disconnect", "Disconnect organization": "Disconnect organization", "Do Not Disturb": "Do Not Disturb", "Download App Logs": "Download App Logs", + "Download Complete": "Download Complete", + "Download failed": "Download failed", "Edit": "Edit", "Edit Shortcuts": "Edit Shortcuts", "Emoji & Symbols": "Emoji & Symbols", + "Enable Do Not Disturb": "Enable Do Not Disturb", "Enable auto updates": "Enable auto updates", "Enable error reporting (requires restart)": "Enable error reporting (requires restart)", "Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)", "Enter Full Screen": "Enter Full Screen", + "Enter Languages": "Enter Languages", "Error saving new organization": "Error saving new organization", "Error saving update notifications": "Error saving update notifications", "Error: {{{error}}}\n\nThe latest version of Zulip Desktop is available at:\n{{{link}}}\nCurrent version: {{{version}}}": "Error: {{{error}}}\n\nThe latest version of Zulip Desktop is available at:\n{{{link}}}\nCurrent version: {{{version}}}", @@ -98,15 +107,20 @@ "New servers added. Reload app now?": "New servers added. Reload app now?", "No": "No", "No Suggestion Found": "No Suggestion Found", + "No unread messages": "No unread messages", "No updates available.": "No updates available.", "Notification settings": "Notification settings", "OK": "OK", "OR": "OR", "On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.", + "Opening {{{link}}}…": "Opening {{{link}}}…", "Organization URL": "Organization URL", "Organizations": "Organizations", + "PAC script": "PAC script", "Paste": "Paste", "Paste and Match Style": "Paste and Match Style", + "Please contact your system administrator.": "Please contact your system administrator.", + "Press {{{exitKey}}} to exit full screen": "Press {{{exitKey}}} to exit full screen", "Proxy": "Proxy", "Proxy bypass rules": "Proxy bypass rules", "Proxy rules": "Proxy rules", @@ -114,9 +128,11 @@ "Quit": "Quit", "Quit Zulip": "Quit Zulip", "Quit when the window is closed": "Quit when the window is closed", + "Redirecting": "Redirecting", "Redo": "Redo", "Release Notes": "Release Notes", "Reload": "Reload", + "Removing {{{url}}} is a restricted operation.": "Removing {{{url}}} is a restricted operation.", "Report an Issue": "Report an Issue", "Reset App Settings": "Reset App Settings", "Reset the application, thus deleting all the connected organizations and accounts.": "Reset the application, thus deleting all the connected organizations and accounts.", @@ -125,6 +141,7 @@ "Select Download Location": "Select Download Location", "Select file": "Select file", "Services": "Services", + "Setting locked by system administrator.": "Setting locked by system administrator.", "Settings": "Settings", "Shortcuts": "Shortcuts", "Show app icon in system tray": "Show app icon in system tray", @@ -155,17 +172,24 @@ "Unknown error": "Unknown error", "Upload": "Upload", "Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)", + "Verify that it works and then click Reconnect.": "Verify that it works and then click Reconnect.", "View": "View", "View Shortcuts": "View Shortcuts", "We encountered an error while saving the update notifications.": "We encountered an error while saving the update notifications.", + "When the application restarts, it will be as if you have just downloaded the Zulip app.": "When the application restarts, it will be as if you have just downloaded the Zulip app.", "Window": "Window", "Window Shortcuts": "Window Shortcuts", "Yes": "Yes", "You are running the latest version of Zulip Desktop.\nVersion: {{{version}}}": "You are running the latest version of Zulip Desktop.\nVersion: {{{version}}}", "You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.", + "Your internet connection doesn't seem to work properly!": "Your internet connection doesn't seem to work properly!", "Zoom In": "Zoom In", "Zoom Out": "Zoom Out", + "Zulip": "Zulip", + "Zulip Update": "Zulip Update", "keyboard shortcuts": "keyboard shortcuts", - "script": "script", + "your-organization.zulipchat.com or zulip.your-organization.com": "your-organization.zulipchat.com or zulip.your-organization.com", + "{number, plural, one {# unread message} other {# unread messages}}": "{number, plural, one {# unread message} other {# unread messages}}", + "{number, plural, one {Could not add # organization} other {Could not add # organizations}}": "{number, plural, one {Could not add # organization} other {Could not add # organizations}}", "{{{server}}} runs an outdated Zulip Server version {{{version}}}. It may not fully work in this app.": "{{{server}}} runs an outdated Zulip Server version {{{version}}}. It may not fully work in this app." }