Mark more strings for translation.

Fixes #1128 among many other things.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2025-07-31 16:22:38 -07:00
committed by Anders Kaseorg
parent 11e2635aa0
commit 9dd5fd2aa5
14 changed files with 98 additions and 47 deletions

View File

@@ -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<void> {
if (["http:", "https:", "mailto:"].includes(url.protocol)) {
@@ -21,7 +22,7 @@ export async function openBrowser(url: URL): Promise<void> {
<head>
<meta charset="UTF-8" />
<meta http-equiv="Refresh" content="0; url=${url.href}" />
<title>Redirecting</title>
<title>${t.__("Redirecting")}</title>
<style>
html {
font-family: menu, "Helvetica Neue", sans-serif;
@@ -29,7 +30,13 @@ export async function openBrowser(url: URL): Promise<void> {
</style>
</head>
<body>
<p>Opening <a href="${url.href}">${url.href}</a>…</p>
<p>
${new Html({
html: t.__("Opening {{{link}}}…", {
link: html`<a href="${url.href}">${url.href}</a>`.html,
}),
})}
</p>
</body>
</html>
`.html,

View File

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

View File

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

View File

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

View File

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

View File

@@ -80,7 +80,6 @@ export class ServerManagerView {
$dndTooltip: HTMLElement;
$sidebar: Element;
$fullscreenPopup: Element;
$fullscreenEscapeKey: string;
loading: Set<string>;
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";

View File

@@ -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`<label
class="disallowed"
title="Setting locked by system administrator."
title="${t.__("Setting locked by system administrator.")}"
></label>`
: html`<label></label>`;
if (settingOption) {

View File

@@ -561,8 +561,9 @@ export function initGeneralSection({$root}: GeneralSectionProperties): void {
}
async function factoryResetSettings(): Promise<void> {
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`
<div class="setting-description">${t.__("Spellchecker Languages")}</div>
<div id="spellcheck-langs-value">
<input name="spellcheck" placeholder="Enter Languages" />
<input name="spellcheck" placeholder="${t.__("Enter Languages")}" />
</div>
`.html;

View File

@@ -28,7 +28,7 @@ export function initNetworkSection({$root}: NetworkSectionProperties): void {
</div>
<div class="manual-proxy-block">
<div class="setting-row" id="proxy-pac-option">
<span class="setting-input-key">PAC ${t.__("script")}</span>
<span class="setting-input-key">${t.__("PAC script")}</span>
<input
class="setting-input-value"
placeholder="e.g. foobar.com/pacfile.js"

View File

@@ -23,7 +23,9 @@ export function initNewServerForm({
<input
class="setting-input-value"
autofocus
placeholder="your-organization.zulipchat.com or zulip.your-organization.com"
placeholder="${t.__(
"your-organization.zulipchat.com or zulip.your-organization.com",
)}"
/>
</div>
<div class="server-center">
@@ -60,12 +62,12 @@ export function initNewServerForm({
)!;
async function submitFormHandler(): Promise<void> {
$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:

View File

@@ -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<Channel extends keyof RendererMessage>(
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}`},
),
);
}
}
});

View File

@@ -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`
<div>Your internet connection doesn't seem to work properly!</div>
<div>Verify that it works and then click try again.</div>
<div>
${t.__("Your internet connection doesn't seem to work properly!")}
</div>
<div>${t.__("Verify that it works and then click Reconnect.")}</div>
`.html;
}

View File

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

View File

@@ -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."
}