Compare commits

...

65 Commits

Author SHA1 Message Date
Anders Kaseorg
7fba8cfae9 release: New release v5.10.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 16:15:08 -07:00
Anders Kaseorg
32301656cc Upgrade dependencies, including Electron 24.2.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 16:15:04 -07:00
Anders Kaseorg
0e16283a37 stylelint: Fix declaration-block-no-redundant-longhand-properties.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 16:15:04 -07:00
Anders Kaseorg
d86482a804 stylelint: Fix media-feature-range-notation.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 16:15:04 -07:00
Anders Kaseorg
3af350e4dc translations: Update translations from Transifex.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 16:14:26 -07:00
Anders Kaseorg
39fc2053c5 translations: Update en.json.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 16:10:46 -07:00
Anders Kaseorg
044f1fd0f9 preference: Fix server icon display in connected organizations list.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 15:30:51 -07:00
Anders Kaseorg
10fb0a82f9 preload: Drop compatibility code for Zulip Server < 4.0.
The server was updated in bfd9999cf874e506592fda254dfe0fe06b5b2738
(4.0-rc1~2192) to expose a proper API for this functionality, so we
don’t need to trigger fake click events to access it.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:41:25 -07:00
Anders Kaseorg
123bd5b2c0 preload: Drop compatibility injected JS for Zulip Server < 3.0.
The server was updated in a6fee2f18ef9d2ef6ac248e9ed82d580daff1a07
(3.0-dev~1674) and e701f208619b8b9b28a85f84ee16cf8d8df82b72
(3.0-dev~1667) to avoid relying on this wrapper.  We no longer support
servers older than 3.0, so we can delete it.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:39:41 -07:00
Anders Kaseorg
ad771c3da8 Display a banner for unsupported Zulip Server versions.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:37:32 -07:00
Anders Kaseorg
4c58bc3aa3 webview: Add a wrapper pane around the real <webview>.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:35:14 -07:00
Anders Kaseorg
9a8680d209 webview: Use private methods.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:35:14 -07:00
Anders Kaseorg
1569890f4d webview: Use private members.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:35:14 -07:00
Anders Kaseorg
2ed400c23c webview: Add destroy method.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:35:14 -07:00
Anders Kaseorg
70621431dc translations: Update translations from Transifex.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:32:11 -07:00
Anders Kaseorg
55b7e09796 tx: Migrate configuration to current Transifex CLI.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:28:06 -07:00
Anders Kaseorg
de2829a968 translations: Update en.json.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:23:21 -07:00
Anders Kaseorg
296de41779 translation-util: Expose the full functionality of __.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:23:21 -07:00
Anders Kaseorg
8b9ebeee25 Fix more typos.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-05 14:23:21 -07:00
Anders Kaseorg
76e81ca337 Fix updating of server names and icons at startup.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-04 15:12:18 -07:00
Anders Kaseorg
2e7a9bb4ed server-tab: Encapsulate setName and setIcon.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-05-04 15:12:18 -07:00
Anders Kaseorg
77638f6287 Fix handling of server icon updates and errors.
Fixes #1283.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-21 15:37:28 -07:00
Anders Kaseorg
6e8fe36876 Fix typos.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-19 13:50:40 -07:00
Anders Kaseorg
2eea4a32a5 preference: Fix CSS in Vite dev mode.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-18 14:21:06 -07:00
Anders Kaseorg
677dfe425c xo: Remove redundant exclusion of unicorn/prefer-json-parse-buffer.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-18 14:03:45 -07:00
Anders Kaseorg
1da3ec545a Don’t show visual notifications when they’re turned off.
Fixes #1299.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-18 13:13:12 -07:00
Anders Kaseorg
3cb6ea4694 Handle exceptions when reading server icons.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-18 12:49:09 -07:00
Anders Kaseorg
0cb7297017 preference: Fix spellchecker languages dropdown positioning.
Apparently the Tagify defaults don’t work inside a shadow root.

Fixes #1286.  Closes #1290.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-04 15:24:22 -07:00
Anders Kaseorg
b8d7003446 Use Zod 3 style for importing Zod.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-03-04 00:23:00 -08:00
Anders Kaseorg
6d27cf8c7d release: New release v5.9.5.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 21:17:32 -08:00
Anders Kaseorg
1ac2483cc4 Upgrade dependencies, including Electron 22.2.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 21:14:43 -08:00
Anders Kaseorg
4d3420dcd0 vite: Externalize gatemaker.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 21:13:23 -08:00
Anders Kaseorg
38450a9aed vite: Don’t externalize dependencies.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 19:14:19 -08:00
Anders Kaseorg
24de7ebb97 webview: Remove did-navigate workaround
The Electron bug seems to have been fixed upstream.  Meanwhile, the
workaround had been causing the app to hang if it can’t connect to an
organization at startup.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:40 -08:00
Anders Kaseorg
5a571d66d0 Enable Chromium sandboxing for remote webviews.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
0ae998a51e Move clipboard decryption to main process.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
447dd18b8b Read injected.js from main process.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
9a200dc40c Replace remote wrapper module with Vite alias.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
d42b752ac1 Bundle with Vite.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
2f4103248d Move icons and sounds to public/resources.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
985d731d2b Move translations to public/translations.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
032f95150c renderer: Add async constructors for functional tabs.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
d1aa5778c3 renderer: Set the icon src to a data: URL.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
Anders Kaseorg
13ce24b75e webview: Remove unnecessary __dirname resolution of customCss.
We’ve already checked that the file exists without resolving via
__dirname.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-06 18:57:22 -08:00
fwcd
c89ec2faf1 Update installation instructions for macOS 2023-02-01 21:50:19 -08:00
Anders Kaseorg
56ab0833b8 release: New release v5.9.4.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-04 16:29:48 -08:00
Anders Kaseorg
c62b393c52 Set quarantine attribute for downloads on macOS.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-04 16:12:31 -08:00
Anders Kaseorg
991de77cad Restore default macOS security settings.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 20:36:13 -08:00
Anders Kaseorg
94780c44c8 handle-external-link: Ignore invalid URLs.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 18:06:36 -08:00
Anders Kaseorg
82542a6390 packaging: Synchronize deb-after-install.sh with upstream.
https://github.com/electron-userland/electron-builder/blob/v23.6.0/packages/app-builder-lib/templates/linux/after-install.tpl

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 17:14:50 -08:00
Anders Kaseorg
53ff8443dc Upgrade dependencies, including Electron 22.0.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:17:24 -08:00
Anders Kaseorg
3855ecab58 Disable sandboxing for now.
Sandboxing will default to enabled in Electron ≥ 20, but we don’t
support it yet.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:17:24 -08:00
Anders Kaseorg
a57cbb4aa8 package.json: Bump engines to node ≥ 16.13.2.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
56a4461c2a xo: Fix n/file-extension-in-import, maybe.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
cd023ec5ab xo: Fix @typescript-eslint/consistent-type-definitions.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
1aa4ade3c0 xo: Fix @typescript-eslint/parameter-properties.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
dcb46eef4f xo: Fix @typescript-eslint/no-useless-empty-export.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
e3e8ef6e3e xo: Fix @typescript-eslint/consistent-generic-constructors.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
6808b1971a xo: Fix unicorn/switch-case-braces.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
1dd5269549 xo: Fix unicorn/prefer-node-protocol.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
d33adca1e8 xo: Fix unicorn/prefer-logical-operator-over-ternary.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 16:05:28 -08:00
Anders Kaseorg
8ea7f7864f autoupdater: Add const assertion.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 14:09:17 -08:00
Anders Kaseorg
493ae06e52 Reformat with Prettier.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-03 14:08:23 -08:00
Anders Kaseorg
2b8f3536d3 Fix E2E tests broken by chat.zulip.org web-public streams.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-12-14 22:41:35 -08:00
Anders Kaseorg
544d23ec09 how-to-install: Update APT instructions.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-07-15 15:36:41 -07:00
139 changed files with 6002 additions and 13049 deletions

6
.gitignore vendored
View File

@@ -8,7 +8,8 @@
.transifexrc
# Compiled binary build directory
dist/
/dist/
/dist-electron/
#snap generated files
snap/parts
@@ -39,6 +40,3 @@ config.gypi
# tests/package.json
.python-version
# Ignore all the typescript compiled files
app/**/*.js

View File

@@ -1,3 +1,3 @@
/app/**/*.js
/app/translations/*.json
/dist
/dist-electron
/public/translations/*.json

View File

@@ -1,5 +1,5 @@
{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"],
"extends": ["stylelint-config-standard"],
"rules": {
"color-named": "never",
"color-no-hex": true,

View File

@@ -1,9 +1,9 @@
[main]
host = https://www.transifex.com
[zulip.desktopjson]
file_filter = app/translations/<lang>.json
[o:zulip:p:zulip:r:desktopjson]
file_filter = public/translations/<lang>.json
minimum_perc = 0
source_file = app/translations/en.json
source_file = public/translations/en.json
source_lang = en
type = KEYVALUEJSON

View File

@@ -10,7 +10,7 @@ Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If yo
## Community
- The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
- The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip web app project, and testing, can be read [here](https://zulip.readthedocs.io).
- If you have any questions regarding zulip-desktop, open an [issue](https://github.com/zulip/zulip-desktop/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).

View File

@@ -24,9 +24,9 @@ Please see the [installation guide](https://zulip.com/help/desktop-app-install-g
# Reporting issues
This desktop client shares most of its code with the Zulip webapp.
This desktop client shares most of its code with the Zulip web app.
Issues in an individual organization's Zulip window should be reported
in the [Zulip server and webapp
in the [Zulip server and web app
project](https://github.com/zulip/zulip/issues/new). Other
issues in the desktop app and its settings should be reported [in this
project](https://github.com/zulip/zulip-desktop/issues/new).

View File

@@ -1,4 +1,4 @@
import * as z from "zod";
import {z} from "zod";
export const dndSettingsSchemata = {
showNotification: z.boolean(),

View File

@@ -4,15 +4,15 @@ import path from "node:path";
import * as Sentry from "@sentry/electron";
import {JsonDB} from "node-json-db";
import {DataError} from "node-json-db/dist/lib/Errors";
import type * as z from "zod";
import type {z} from "zod";
import {app, dialog} from "zulip:remote";
import {configSchemata} from "./config-schemata";
import * as EnterpriseUtil from "./enterprise-util";
import Logger from "./logger-util";
import {app, dialog} from "./remote";
import {configSchemata} from "./config-schemata.js";
import * as EnterpriseUtil from "./enterprise-util.js";
import Logger from "./logger-util.js";
export type Config = {
[Key in keyof typeof configSchemata]: z.output<typeof configSchemata[Key]>;
[Key in keyof typeof configSchemata]: z.output<(typeof configSchemata)[Key]>;
};
const logger = new Logger({
@@ -26,7 +26,7 @@ reloadDb();
export function getConfigItem<Key extends keyof Config>(
key: Key,
defaultValue: Config[Key],
): z.output<typeof configSchemata[Key]> {
): z.output<(typeof configSchemata)[Key]> {
try {
db.reload();
} catch (error: unknown) {

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import {app} from "./remote";
import {app} from "zulip:remote";
let setupCompleted = false;

View File

@@ -1,22 +1,22 @@
import process from "node:process";
import type * as z from "zod";
import type {z} from "zod";
import type {dndSettingsSchemata} from "./config-schemata";
import * as ConfigUtil from "./config-util";
import type {dndSettingsSchemata} from "./config-schemata.js";
import * as ConfigUtil from "./config-util.js";
export type DndSettings = {
[Key in keyof typeof dndSettingsSchemata]: z.output<
typeof dndSettingsSchemata[Key]
(typeof dndSettingsSchemata)[Key]
>;
};
type SettingName = keyof DndSettings;
interface Toggle {
type Toggle = {
dnd: boolean;
newSettings: Partial<DndSettings>;
}
};
export function toggle(): Toggle {
const dnd = !ConfigUtil.getConfigItem("dnd", false);

View File

@@ -2,14 +2,14 @@ import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import * as z from "zod";
import {z} from "zod";
import {enterpriseConfigSchemata} from "./config-schemata";
import Logger from "./logger-util";
import {enterpriseConfigSchemata} from "./config-schemata.js";
import Logger from "./logger-util.js";
type EnterpriseConfig = {
[Key in keyof typeof enterpriseConfigSchemata]: z.output<
typeof enterpriseConfigSchemata[Key]
(typeof enterpriseConfigSchemata)[Key]
>;
};

View File

@@ -3,7 +3,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {html} from "./html";
import {html} from "./html.js";
export async function openBrowser(url: URL): Promise<void> {
if (["http:", "https:", "mailto:"].includes(url.protocol)) {

View File

@@ -1,14 +1,15 @@
import {Console} from "node:console";
import {Console} from "node:console"; // eslint-disable-line n/prefer-global/console
import fs from "node:fs";
import os from "node:os";
import process from "node:process";
import {initSetUp} from "./default-util";
import {app} from "./remote";
import {app} from "zulip:remote";
interface LoggerOptions {
import {initSetUp} from "./default-util.js";
type LoggerOptions = {
file?: string;
}
};
initSetUp();

View File

@@ -1,7 +1,7 @@
interface DialogBoxError {
type DialogBoxError = {
title: string;
content: string;
}
};
export function invalidZulipServerError(domain: string): string {
return `${domain} does not appear to be a valid Zulip server. Make sure that

15
app/common/paths.ts Normal file
View File

@@ -0,0 +1,15 @@
import path from "node:path";
import process from "node:process";
import url from "node:url";
export const bundlePath = __dirname;
export const publicPath = import.meta.env.DEV
? path.join(bundlePath, "../public")
: bundlePath;
export const bundleUrl = import.meta.env.DEV
? process.env.VITE_DEV_SERVER_URL
: url.pathToFileURL(__dirname).href + "/";
export const publicUrl = bundleUrl;

View File

@@ -1,8 +0,0 @@
import process from "node:process";
export const {app, dialog} =
process.type === "renderer"
? // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
(require("@electron/remote") as typeof import("@electron/remote"))
: // eslint-disable-next-line @typescript-eslint/no-require-imports
require("electron/main");

View File

@@ -2,17 +2,15 @@ import path from "node:path";
import i18n from "i18n";
import * as ConfigUtil from "./config-util";
import * as ConfigUtil from "./config-util.js";
import {publicPath} from "./paths.js";
i18n.configure({
directory: path.join(__dirname, "../translations/"),
directory: path.join(publicPath, "translations/"),
updateFiles: false,
});
/* Fetches the current appLocale from settings.json */
const appLocale = ConfigUtil.getConfigItem("appLanguage", "en");
i18n.setLocale(ConfigUtil.getConfigItem("appLanguage", "en") ?? "en");
/* If no locale present in the json, en is set default */
export function __(phrase: string): string {
return i18n.__({phrase, locale: appLocale ? appLocale : "en"});
}
export {__} from "i18n";

View File

@@ -1,12 +1,13 @@
import type {DndSettings} from "./dnd-util";
import type {MenuProps, ServerConf} from "./types";
import type {DndSettings} from "./dnd-util.js";
import type {MenuProps, ServerConf} from "./types.js";
export interface MainMessage {
export type MainMessage = {
"clear-app-settings": () => void;
"configure-spell-checker": () => void;
"fetch-user-agent": () => string;
"focus-app": () => void;
"focus-this-webview": () => void;
"new-clipboard-key": () => {key: Uint8Array; sig: Uint8Array};
"permission-callback": (permissionCallbackId: number, grant: boolean) => void;
"quit-app": () => void;
"realm-icon-changed": (serverURL: string, iconURL: string) => void;
@@ -22,15 +23,16 @@ export interface MainMessage {
"update-badge": (messageCount: number) => void;
"update-menu": (props: MenuProps) => void;
"update-taskbar-icon": (data: string, text: string) => void;
}
};
export interface MainCall {
export type MainCall = {
"get-server-settings": (domain: string) => ServerConf;
"is-online": (url: string) => boolean;
"save-server-icon": (iconURL: string) => string;
}
"poll-clipboard": (key: Uint8Array, sig: Uint8Array) => string | undefined;
"save-server-icon": (iconURL: string) => string | null;
};
export interface RendererMessage {
export type RendererMessage = {
back: () => void;
"copy-zulip-url": () => void;
destroytray: () => void;
@@ -74,9 +76,9 @@ export interface RendererMessage {
toggletray: () => void;
tray: (arg: number) => void;
"update-realm-icon": (serverURL: string, iconURL: string) => void;
"update-realm-name": (serveRURL: string, realmName: string) => void;
"update-realm-name": (serverURL: string, realmName: string) => void;
"webview-reload": () => void;
zoomActualSize: () => void;
zoomIn: () => void;
zoomOut: () => void;
}
};

View File

@@ -1,8 +1,8 @@
export interface MenuProps {
export type MenuProps = {
tabs: TabData[];
activeTabIndex?: number;
enableMenu?: boolean;
}
};
export type NavItem =
| "General"
@@ -11,16 +11,18 @@ export type NavItem =
| "Organizations"
| "Shortcuts";
export interface ServerConf {
export type ServerConf = {
url: string;
alias: string;
icon: string;
}
zulipVersion: string;
zulipFeatureLevel: number;
};
export type TabRole = "server" | "function";
export interface TabData {
export type TabData = {
role: TabRole;
name: string;
index: number;
}
};

View File

@@ -6,9 +6,9 @@ import log from "electron-log";
import type {UpdateDownloadedEvent, UpdateInfo} from "electron-updater";
import {autoUpdater} from "electron-updater";
import * as ConfigUtil from "../common/config-util";
import * as ConfigUtil from "../common/config-util.js";
import {linuxUpdateNotification} from "./linuxupdater"; // Required only in case of linux
import {linuxUpdateNotification} from "./linuxupdater.js"; // Required only in case of linux
let quitting = false;
@@ -30,7 +30,7 @@ export async function appUpdater(updateFromMenu = false): Promise<void> {
let updateAvailable = false;
// Log whats happening
// Log what's happening
log.transports.file.fileName = "updates.log";
log.transports.file.level = "info";
autoUpdater.logger = log;
@@ -40,7 +40,10 @@ export async function appUpdater(updateFromMenu = false): Promise<void> {
autoUpdater.allowPrerelease = isBetaUpdate;
const eventsListenerRemove = ["update-available", "update-not-available"];
const eventsListenerRemove = [
"update-available",
"update-not-available",
] as const;
autoUpdater.on("update-available", async (info: UpdateInfo) => {
if (updateFromMenu) {
updateAvailable = true;

View File

@@ -3,9 +3,9 @@ import type {BrowserWindow} from "electron/main";
import {app} from "electron/main";
import process from "node:process";
import * as ConfigUtil from "../common/config-util";
import * as ConfigUtil from "../common/config-util.js";
import {send} from "./typed-ipc-main";
import {send} from "./typed-ipc-main.js";
function showBadgeCount(messageCount: number, mainWindow: BrowserWindow): void {
if (process.platform === "win32") {

View File

@@ -8,10 +8,10 @@ import {Notification, app} from "electron/main";
import fs from "node:fs";
import path from "node:path";
import * as ConfigUtil from "../common/config-util";
import * as LinkUtil from "../common/link-util";
import * as ConfigUtil from "../common/config-util.js";
import * as LinkUtil from "../common/link-util.js";
import {send} from "./typed-ipc-main";
import {send} from "./typed-ipc-main.js";
function isUploadsUrl(server: string, url: URL): boolean {
return url.origin === server && url.pathname.startsWith("/user_uploads/");
@@ -105,7 +105,13 @@ export default function handleExternalLink(
details: HandlerDetails,
mainContents: WebContents,
): void {
const url = new URL(details.url);
let url: URL;
try {
url = new URL(details.url);
} catch {
return;
}
const downloadPath = ConfigUtil.getConfigItem(
"downloadsPath",
`${app.getPath("downloads")}`,

View File

@@ -1,23 +1,29 @@
import {clipboard} from "electron/common";
import type {IpcMainEvent, WebContents} from "electron/main";
import {BrowserWindow, app, dialog, powerMonitor, session} from "electron/main";
import {Buffer} from "node:buffer";
import crypto from "node:crypto";
import path from "node:path";
import process from "node:process";
import * as remoteMain from "@electron/remote/main";
import windowStateKeeper from "electron-window-state";
import * as ConfigUtil from "../common/config-util";
import type {RendererMessage} from "../common/typed-ipc";
import type {MenuProps} from "../common/types";
import * as ConfigUtil from "../common/config-util.js";
import {bundlePath, bundleUrl, publicPath} from "../common/paths.js";
import type {RendererMessage} from "../common/typed-ipc.js";
import type {MenuProps} from "../common/types.js";
import {appUpdater, shouldQuitForUpdate} from "./autoupdater";
import * as BadgeSettings from "./badge-settings";
import handleExternalLink from "./handle-external-link";
import * as AppMenu from "./menu";
import {_getServerSettings, _isOnline, _saveServerIcon} from "./request";
import {sentryInit} from "./sentry";
import {setAutoLaunch} from "./startup";
import {ipcMain, send} from "./typed-ipc-main";
import {appUpdater, shouldQuitForUpdate} from "./autoupdater.js";
import * as BadgeSettings from "./badge-settings.js";
import handleExternalLink from "./handle-external-link.js";
import * as AppMenu from "./menu.js";
import {_getServerSettings, _isOnline, _saveServerIcon} from "./request.js";
import {sentryInit} from "./sentry.js";
import {setAutoLaunch} from "./startup.js";
import {ipcMain, send} from "./typed-ipc-main.js";
import "gatemaker/electron-setup"; // eslint-disable-line import/no-unassigned-import
// eslint-disable-next-line @typescript-eslint/naming-convention
const {GDK_BACKEND} = process.env;
@@ -33,13 +39,13 @@ let badgeCount: number;
let isQuitting = false;
// Load this url in main window
const mainUrl = "file://" + path.join(__dirname, "../renderer", "main.html");
// Load this file in main window
const mainUrl = new URL("app/renderer/main.html", bundleUrl).href;
const permissionCallbacks = new Map<number, (grant: boolean) => void>();
let nextPermissionCallbackId = 0;
const appIcon = path.join(__dirname, "../resources", "Icon");
const appIcon = path.join(publicPath, "resources/Icon");
const iconPath = (): string =>
appIcon + (process.platform === "win32" ? ".ico" : ".png");
@@ -72,7 +78,8 @@ function createMainWindow(): BrowserWindow {
minWidth: 500,
minHeight: 400,
webPreferences: {
preload: require.resolve("../renderer/js/main"),
preload: path.join(bundlePath, "renderer.js"),
sandbox: false,
webviewTag: true,
},
show: false,
@@ -198,6 +205,42 @@ function createMainWindow(): BrowserWindow {
configureSpellChecker();
ipcMain.on("configure-spell-checker", configureSpellChecker);
const clipboardSigKey = crypto.randomBytes(32);
ipcMain.on("new-clipboard-key", (event) => {
const key = crypto.randomBytes(32);
const hmac = crypto.createHmac("sha256", clipboardSigKey);
hmac.update(key);
event.returnValue = {key, sig: hmac.digest()};
});
ipcMain.handle("poll-clipboard", (event, key, sig) => {
// Check that the key was generated here.
const hmac = crypto.createHmac("sha256", clipboardSigKey);
hmac.update(key);
if (!crypto.timingSafeEqual(sig, hmac.digest())) return;
try {
// Check that the data on the clipboard was encrypted to the key.
const data = Buffer.from(clipboard.readText(), "hex");
const iv = data.slice(0, 12);
const ciphertext = data.slice(12, -16);
const authTag = data.slice(-16);
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv, {
authTagLength: 16,
});
decipher.setAuthTag(authTag);
return (
decipher.update(ciphertext, undefined, "utf8") + decipher.final("utf8")
);
} catch {
// If the parsing or decryption failed in any way,
// the correct token hasnt been copied yet; try
// again next time.
return undefined;
}
});
AppMenu.setMenu({
tabs: [],
});

View File

@@ -5,7 +5,7 @@ import path from "node:path";
import {JsonDB} from "node-json-db";
import {DataError} from "node-json-db/dist/lib/Errors";
import Logger from "../common/logger-util";
import Logger from "../common/logger-util.js";
const logger = new Logger({
file: "linux-update-util.log",

View File

@@ -3,13 +3,13 @@ import {Notification, app, net} from "electron/main";
import getStream from "get-stream";
import * as semver from "semver";
import * as z from "zod";
import {z} from "zod";
import * as ConfigUtil from "../common/config-util";
import Logger from "../common/logger-util";
import * as ConfigUtil from "../common/config-util.js";
import Logger from "../common/logger-util.js";
import * as LinuxUpdateUtil from "./linux-update-util";
import {fetchResponse} from "./request";
import * as LinuxUpdateUtil from "./linux-update-util.js";
import {fetchResponse} from "./request.js";
const logger = new Logger({
file: "linux-update-util.log",

View File

@@ -5,14 +5,14 @@ import process from "node:process";
import AdmZip from "adm-zip";
import * as ConfigUtil from "../common/config-util";
import * as DNDUtil from "../common/dnd-util";
import * as t from "../common/translation-util";
import type {RendererMessage} from "../common/typed-ipc";
import type {MenuProps, TabData} from "../common/types";
import * as ConfigUtil from "../common/config-util.js";
import * as DNDUtil from "../common/dnd-util.js";
import * as t from "../common/translation-util.js";
import type {RendererMessage} from "../common/typed-ipc.js";
import type {MenuProps, TabData} from "../common/types.js";
import {appUpdater} from "./autoupdater";
import {send} from "./typed-ipc-main";
import {appUpdater} from "./autoupdater.js";
import {send} from "./typed-ipc-main.js";
const appName = app.name;

View File

@@ -7,11 +7,11 @@ import util from "node:util";
import * as Sentry from "@sentry/electron";
import getStream from "get-stream";
import * as z from "zod";
import {z} from "zod";
import Logger from "../common/logger-util";
import * as Messages from "../common/messages";
import type {ServerConf} from "../common/types";
import Logger from "../common/logger-util.js";
import * as Messages from "../common/messages.js";
import type {ServerConf} from "../common/types.js";
export async function fetchResponse(
request: ClientRequest,
@@ -30,8 +30,6 @@ const pipeline = util.promisify(stream.pipeline);
/* Request: domain-util */
const defaultIconUrl = "../renderer/img/icon.png";
const logger = new Logger({
file: "domain-util.log",
});
@@ -73,11 +71,19 @@ export const _getServerSettings = async (
const data: unknown = JSON.parse(await getStream(response));
/* eslint-disable @typescript-eslint/naming-convention */
const {realm_name, realm_uri, realm_icon} = z
const {
realm_name,
realm_uri,
realm_icon,
zulip_version,
zulip_feature_level,
} = z
.object({
realm_name: z.string(),
realm_uri: z.string(),
realm_uri: z.string().url(),
realm_icon: z.string(),
zulip_version: z.string().default("unknown"),
zulip_feature_level: z.number().default(0),
})
.parse(data);
/* eslint-enable @typescript-eslint/naming-convention */
@@ -88,18 +94,20 @@ export const _getServerSettings = async (
icon: realm_icon.startsWith("/") ? realm_uri + realm_icon : realm_icon,
url: realm_uri,
alias: realm_name,
zulipVersion: zulip_version,
zulipFeatureLevel: zulip_feature_level,
};
};
export const _saveServerIcon = async (
url: string,
session: Session,
): Promise<string> => {
): Promise<string | null> => {
try {
const response = await fetchResponse(net.request({url, session}));
if (response.statusCode !== 200) {
logger.log("Could not get server icon.");
return defaultIconUrl;
return null;
}
const filePath = generateFilePath(url);
@@ -109,7 +117,7 @@ export const _saveServerIcon = async (
logger.log("Could not get server icon.");
logger.log(error);
Sentry.captureException(error);
return defaultIconUrl;
return null;
}
};

View File

@@ -1,8 +1,8 @@
import {app} from "electron/main";
import * as Sentry from "@sentry/electron";
import * as Sentry from "@sentry/electron/main"; // eslint-disable-line n/file-extension-in-import
import {getConfigItem} from "../common/config-util";
import {getConfigItem} from "../common/config-util.js";
export const sentryInit = (): void => {
Sentry.init({

View File

@@ -3,7 +3,7 @@ import process from "node:process";
import AutoLaunch from "auto-launch";
import * as ConfigUtil from "../common/config-util";
import * as ConfigUtil from "../common/config-util.js";
export const setAutoLaunch = async (
AutoLaunchValue: boolean,

26
app/renderer/about.html Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<meta charset="UTF-8" />
<link rel="stylesheet" href="css/about.css" />
<!-- Initially hidden to prevent FOUC -->
<div class="about" hidden>
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version"></p>
<div class="maintenance-info">
<p class="detail maintainer">
Maintained by
<a href="https://zulip.com" target="_blank" rel="noopener noreferrer"
>Zulip</a
>
</p>
<p class="detail license">
Available under the
<a
href="https://github.com/zulip/zulip-desktop/blob/main/LICENSE"
target="_blank"
rel="noopener noreferrer"
>Apache 2.0 License</a
>
</p>
</div>
</div>

View File

@@ -303,7 +303,7 @@ body {
visibility: hidden;
}
webview,
.webview-pane,
.functional-view {
position: absolute;
width: 100%;
@@ -312,7 +312,16 @@ webview,
visibility: hidden;
}
webview.active,
.webview-pane {
display: flex;
flex-direction: column;
}
.webview-pane > webview {
flex: 1;
}
.webview-pane.active,
.functional-view.active {
z-index: 1;
visibility: visible;
@@ -322,6 +331,30 @@ webview.focus {
outline: 0 solid transparent;
}
.webview-unsupported {
background: rgb(254 243 199);
border: 1px solid rgb(253 230 138);
color: rgb(69 26 3);
font-family: system-ui;
font-size: 14px;
display: flex;
}
.webview-unsupported[hidden] {
display: none;
}
.webview-unsupported-message {
padding: 0.3em;
flex: 1;
text-align: center;
}
.webview-unsupported-dismiss {
padding: 0.3em;
cursor: pointer;
}
/* Tooltip styling */
#loading-tooltip,

View File

@@ -1,3 +1,5 @@
@import url("@yaireo/tagify/dist/tagify.css");
:host {
contain: strict;
display: flow-root;
@@ -10,6 +12,11 @@
letter-spacing: -0.08px;
line-height: 18px;
color: rgb(139 142 143 / 100%);
/* Copied from https://github.com/yairEO/tagify/blob/v4.17.7/src/tagify.scss#L4-L8 */
--tagify-dd-color-primary: rgb(53 149 246);
--tagify-dd-bg-color: rgb(255 255 255);
--tagify-dd-item-pad: 0.3em 0.5em;
}
kbd {
@@ -475,10 +482,7 @@ input.toggle-round + label::after {
input.toggle-round + label::before {
background-color: rgb(241 241 241 / 100%);
border-radius: 25px;
top: 0;
right: 0;
left: 0;
bottom: 0;
inset: 0;
}
input.toggle-round + label::after {
@@ -490,10 +494,7 @@ input.toggle-round + label::after {
input.toggle-round:checked + label::before {
background-color: rgb(78 191 172 / 100%);
top: 0;
right: 0;
left: 0;
bottom: 0;
inset: 0;
}
input.toggle-round:checked + label::after {
@@ -651,7 +652,7 @@ i.open-network-button {
}
/* responsive grid */
@media (min-width: 500px) and (max-width: 720px) {
@media (width >= 500px) and (width <= 720px) {
#new-server-container {
padding-left: 0;
width: 60vw;
@@ -663,7 +664,7 @@ i.open-network-button {
}
}
@media (max-width: 500px) {
@media (width <= 500px) {
#new-server-container {
padding-left: 0;
width: 54%;
@@ -674,7 +675,7 @@ i.open-network-button {
}
}
@media (max-width: 650px) {
@media (width <= 650px) {
.selected-css-path,
.download-folder-path {
margin-right: 15px;
@@ -689,7 +690,7 @@ i.open-network-button {
}
}
@media (max-width: 720px) {
@media (width <= 720px) {
.modal-container {
width: 60vw;
padding: 40px;
@@ -712,7 +713,7 @@ i.open-network-button {
}
}
@media (max-width: 600px) {
@media (width <= 600px) {
.divider {
margin-left: 4%;
}
@@ -724,7 +725,7 @@ i.open-network-button {
}
}
@media (max-width: 900px) {
@media (width <= 900px) {
.settings-card {
flex-direction: column;
align-items: center;
@@ -760,3 +761,9 @@ i.open-network-button {
top: 0;
bottom: 0;
}
.settings-tagify-dropdown {
position: relative;
z-index: 9999;
height: 0;
}

View File

@@ -1,6 +1,4 @@
import {clipboard} from "electron/common";
import {Buffer} from "node:buffer";
import crypto from "node:crypto";
import {ipcRenderer} from "./typed-ipc-renderer.js";
// This helper is exposed via electron_bridge for use in the social
// login flow.
@@ -16,11 +14,11 @@ import crypto from "node:crypto";
// dont leak anything from the users clipboard other than the token
// intended for us.
export interface ClipboardDecrypter {
export type ClipboardDecrypter = {
version: number;
key: Uint8Array;
pasted: Promise<string>;
}
};
export class ClipboardDecrypterImpl implements ClipboardDecrypter {
version: number;
@@ -30,7 +28,8 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
constructor(_: number) {
// At this time, the only version is 1.
this.version = 1;
this.key = crypto.randomBytes(32);
const {key, sig} = ipcRenderer.sendSync("new-clipboard-key");
this.key = key;
this.pasted = new Promise((resolve) => {
let interval: NodeJS.Timeout | null = null;
const startPolling = () => {
@@ -38,7 +37,7 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
interval = setInterval(poll, 1000);
}
poll();
void poll();
};
const stopPolling = () => {
@@ -48,30 +47,9 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
}
};
const poll = () => {
let plaintext;
try {
const data = Buffer.from(clipboard.readText(), "hex");
const iv = data.slice(0, 12);
const ciphertext = data.slice(12, -16);
const authTag = data.slice(-16);
const decipher = crypto.createDecipheriv(
"aes-256-gcm",
this.key,
iv,
{authTagLength: 16},
);
decipher.setAuthTag(authTag);
plaintext =
decipher.update(ciphertext, undefined, "utf8") +
decipher.final("utf8");
} catch {
// If the parsing or decryption failed in any way,
// the correct token hasnt been copied yet; try
// again next time.
return;
}
const poll = async () => {
const plaintext = await ipcRenderer.invoke("poll-clipboard", key, sig);
if (plaintext === undefined) return;
window.removeEventListener("focus", startPolling);
window.removeEventListener("blur", stopPolling);

View File

@@ -1,4 +1,4 @@
import type {Html} from "../../../common/html";
import type {Html} from "../../../common/html.js";
export function generateNodeFromHtml(html: Html): Element {
const wrapper = document.createElement("div");

View File

@@ -8,7 +8,7 @@ import process from "node:process";
import {Menu} from "@electron/remote";
import * as t from "../../../common/translation-util";
import * as t from "../../../common/translation-util.js";
export const contextMenu = (
webContents: WebContents,
@@ -137,7 +137,7 @@ export const contextMenu = (
}
}
// Hide the invisible separators on Linux and Windows
// Electron has a bug which ignores visible: false parameter for separator menuitems. So we remove them here.
// Electron has a bug which ignores visible: false parameter for separator menu items. So we remove them here.
// https://github.com/electron/electron/issues/5869
// https://github.com/electron/electron/issues/6906

View File

@@ -1,13 +1,13 @@
import type {Html} from "../../../common/html";
import {html} from "../../../common/html";
import type {Html} from "../../../common/html.js";
import {html} from "../../../common/html.js";
import {generateNodeFromHtml} from "./base";
import type {TabProps} from "./tab";
import Tab from "./tab";
import {generateNodeFromHtml} from "./base.js";
import type {TabProps} from "./tab.js";
import Tab from "./tab.js";
export interface FunctionalTabProps extends TabProps {
export type FunctionalTabProps = {
$view: Element;
}
} & TabProps;
export default class FunctionalTab extends Tab {
$view: Element;

View File

@@ -1,21 +1,23 @@
import process from "node:process";
import type {Html} from "../../../common/html";
import {html} from "../../../common/html";
import {ipcRenderer} from "../typed-ipc-renderer";
import type {Html} from "../../../common/html.js";
import {html} from "../../../common/html.js";
import {ipcRenderer} from "../typed-ipc-renderer.js";
import {generateNodeFromHtml} from "./base";
import type {TabProps} from "./tab";
import Tab from "./tab";
import type WebView from "./webview";
import {generateNodeFromHtml} from "./base.js";
import type {TabProps} from "./tab.js";
import Tab from "./tab.js";
import type WebView from "./webview.js";
export interface ServerTabProps extends TabProps {
export type ServerTabProps = {
webview: Promise<WebView>;
}
} & TabProps;
export default class ServerTab extends Tab {
webview: Promise<WebView>;
$el: Element;
$name: Element;
$icon: HTMLImageElement;
$badge: Element;
constructor({webview, ...props}: ServerTabProps) {
@@ -25,6 +27,8 @@ export default class ServerTab extends Tab {
this.$el = generateNodeFromHtml(this.templateHtml());
this.props.$root.append(this.$el);
this.registerListeners();
this.$name = this.$el.querySelector(".server-tooltip")!;
this.$icon = this.$el.querySelector(".server-icons")!;
this.$badge = this.$el.querySelector(".server-tab-badge")!;
}
@@ -40,7 +44,7 @@ export default class ServerTab extends Tab {
override async destroy(): Promise<void> {
await super.destroy();
(await this.webview).$el.remove();
(await this.webview).destroy();
}
templateHtml(): Html {
@@ -58,6 +62,16 @@ export default class ServerTab extends Tab {
`;
}
setName(name: string): void {
this.props.name = name;
this.$name.textContent = name;
}
setIcon(icon: string): void {
this.props.icon = icon;
this.$icon.src = icon;
}
updateBadge(count: number): void {
this.$badge.textContent = count > 999 ? "1K+" : count.toString();
this.$badge.classList.toggle("active", count > 0);

View File

@@ -1,6 +1,6 @@
import type {TabRole} from "../../../common/types";
import type {TabRole} from "../../../common/types.js";
export interface TabProps {
export type TabProps = {
role: TabRole;
icon?: string;
name: string;
@@ -12,15 +12,12 @@ export interface TabProps {
onHoverOut?: () => void;
materialIcon?: string;
onDestroy?: () => void;
}
};
export default abstract class Tab {
props: TabProps;
abstract $el: Element;
constructor(props: TabProps) {
this.props = props;
}
constructor(readonly props: TabProps) {}
registerListeners(): void {
this.$el.addEventListener("click", this.props.onClick);

View File

@@ -1,25 +1,25 @@
import type {WebContents} from "electron/main";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import * as remote from "@electron/remote";
import {app, dialog} from "@electron/remote";
import * as ConfigUtil from "../../../common/config-util";
import type {Html} from "../../../common/html";
import {html} from "../../../common/html";
import type {RendererMessage} from "../../../common/typed-ipc";
import type {TabRole} from "../../../common/types";
import {ipcRenderer} from "../typed-ipc-renderer";
import * as SystemUtil from "../utils/system-util";
import * as ConfigUtil from "../../../common/config-util.js";
import type {Html} from "../../../common/html.js";
import {html} from "../../../common/html.js";
import type {RendererMessage} from "../../../common/typed-ipc.js";
import type {TabRole} from "../../../common/types.js";
import preloadCss from "../../css/preload.css?raw"; // eslint-disable-line n/file-extension-in-import
import {ipcRenderer} from "../typed-ipc-renderer.js";
import * as SystemUtil from "../utils/system-util.js";
import {generateNodeFromHtml} from "./base";
import {contextMenu} from "./context-menu";
import {generateNodeFromHtml} from "./base.js";
import {contextMenu} from "./context-menu.js";
const shouldSilentWebview = ConfigUtil.getConfigItem("silent", false);
interface WebViewProps {
type WebViewProps = {
$root: Element;
rootWebContents: WebContents;
index: number;
@@ -32,35 +32,46 @@ interface WebViewProps {
preload?: string;
onTitleChange: () => void;
hasPermission?: (origin: string, permission: string) => boolean;
}
unsupportedMessage?: string;
};
export default class WebView {
static templateHtml(props: WebViewProps): Html {
return html`
<webview
data-tab-id="${props.tabIndex}"
src="${props.url}"
${props.preload === undefined
? html``
: html`preload="${props.preload}"`}
partition="persist:webviewsession"
allowpopups
>
</webview>
<div class="webview-pane">
<div
class="webview-unsupported"
${props.unsupportedMessage === undefined ? html`hidden` : html``}
>
<span class="webview-unsupported-message"
>${props.unsupportedMessage ?? ""}</span
>
<span class="webview-unsupported-dismiss">×</span>
</div>
<webview
data-tab-id="${props.tabIndex}"
src="${props.url}"
${props.preload === undefined
? html``
: html`preload="${props.preload}"`}
partition="persist:webviewsession"
allowpopups
>
</webview>
</div>
`;
}
static async create(props: WebViewProps): Promise<WebView> {
const $element = generateNodeFromHtml(
const $pane = generateNodeFromHtml(
WebView.templateHtml(props),
) as HTMLElement;
props.$root.append($element);
props.$root.append($pane);
// Wait for did-navigate rather than did-attach to work around
// https://github.com/electron/electron/issues/31918
const $webview: HTMLElement = $pane.querySelector(":scope > webview")!;
await new Promise<void>((resolve) => {
$element.addEventListener(
"did-navigate",
$webview.addEventListener(
"did-attach",
() => {
resolve();
},
@@ -89,162 +100,60 @@ export default class WebView {
throw new TypeError("Failed to get WebContents ID");
}
return new WebView(props, $element, webContentsId);
return new WebView(props, $pane, $webview, webContentsId);
}
props: WebViewProps;
zoomFactor: number;
badgeCount: number;
loading: boolean;
customCss: string | false | null;
$webviewsContainer: DOMTokenList;
$el: HTMLElement;
webContentsId: number;
badgeCount = 0;
loading = true;
private zoomFactor = 1;
private customCss: string | false | null;
private readonly $webviewsContainer: DOMTokenList;
private readonly $unsupported: HTMLElement;
private readonly $unsupportedMessage: HTMLElement;
private readonly $unsupportedDismiss: HTMLElement;
private unsupportedDismissed = false;
private constructor(
props: WebViewProps,
$element: HTMLElement,
webContentsId: number,
readonly props: WebViewProps,
private readonly $pane: HTMLElement,
private readonly $webview: HTMLElement,
readonly webContentsId: number,
) {
this.props = props;
this.zoomFactor = 1;
this.loading = true;
this.badgeCount = 0;
this.customCss = ConfigUtil.getConfigItem("customCSS", null);
this.$webviewsContainer = document.querySelector(
"#webviews-container",
)!.classList;
this.$el = $element;
this.webContentsId = webContentsId;
this.$unsupported = $pane.querySelector(".webview-unsupported")!;
this.$unsupportedMessage = $pane.querySelector(
".webview-unsupported-message",
)!;
this.$unsupportedDismiss = $pane.querySelector(
".webview-unsupported-dismiss",
)!;
this.registerListeners();
}
destroy(): void {
this.$pane.remove();
}
getWebContents(): WebContents {
return remote.webContents.fromId(this.webContentsId);
}
registerListeners(): void {
const webContents = this.getWebContents();
if (shouldSilentWebview) {
webContents.setAudioMuted(true);
}
webContents.on("page-title-updated", (_event, title) => {
this.badgeCount = this.getBadgeCount(title);
this.props.onTitleChange();
});
this.$el.addEventListener("did-navigate-in-page", () => {
this.canGoBackButton();
});
this.$el.addEventListener("did-navigate", () => {
this.canGoBackButton();
});
webContents.on("page-favicon-updated", (_event, favicons) => {
// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like
// https://chat.zulip.org/static/images/favicon/favicon-pms.png
if (
favicons[0].indexOf("favicon-pms") > 0 &&
process.platform === "darwin"
) {
// This api is only supported on macOS
app.dock.setBadge("●");
// Bounce the dock
if (ConfigUtil.getConfigItem("dockBouncing", true)) {
app.dock.bounce();
}
}
});
webContents.addListener("context-menu", (event, menuParameters) => {
contextMenu(webContents, event, menuParameters);
});
this.$el.addEventListener("dom-ready", () => {
this.loading = false;
this.props.switchLoading(false, this.props.url);
this.show();
});
webContents.on("did-fail-load", (_event, _errorCode, errorDescription) => {
const hasConnectivityError =
SystemUtil.connectivityError.includes(errorDescription);
if (hasConnectivityError) {
console.error("error", errorDescription);
if (!this.props.url.includes("network.html")) {
this.props.onNetworkError(this.props.index);
}
}
});
this.$el.addEventListener("did-start-loading", () => {
this.props.switchLoading(true, this.props.url);
});
this.$el.addEventListener("did-stop-loading", () => {
this.props.switchLoading(false, this.props.url);
});
}
getBadgeCount(title: string): number {
const messageCountInTitle = /^\((\d+)\)/.exec(title);
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
return remote.webContents.fromId(this.webContentsId)!;
}
showNotificationSettings(): void {
this.send("show-notification-settings");
}
show(): void {
// Do not show WebView if another tab was selected and this tab should be in background.
if (!this.props.isActive()) {
return;
}
// To show or hide the loading indicator in the the active tab
this.$webviewsContainer.toggle("loaded", !this.loading);
this.$el.classList.add("active");
this.focus();
this.props.onTitleChange();
// Injecting preload css in webview to override some css rules
(async () =>
this.getWebContents().insertCSS(
fs.readFileSync(path.join(__dirname, "/../../css/preload.css"), "utf8"),
))();
// Get customCSS again from config util to avoid warning user again
const customCss = ConfigUtil.getConfigItem("customCSS", null);
this.customCss = customCss;
if (customCss) {
if (!fs.existsSync(customCss)) {
this.customCss = null;
ConfigUtil.setConfigItem("customCSS", null);
const errorMessage = "The custom css previously set is deleted!";
dialog.showErrorBox("custom css file deleted!", errorMessage);
return;
}
(async () =>
this.getWebContents().insertCSS(
fs.readFileSync(path.resolve(__dirname, customCss), "utf8"),
))();
}
}
focus(): void {
this.$el.focus();
this.$webview.focus();
// Work around https://github.com/electron/electron/issues/31918
this.$el.shadowRoot?.querySelector("iframe")?.focus();
this.$webview.shadowRoot?.querySelector("iframe")?.focus();
}
hide(): void {
this.$el.classList.remove("active");
this.$pane.classList.remove("active");
}
load(): void {
@@ -307,10 +216,125 @@ export default class WebView {
this.getWebContents().reload();
}
setUnsupportedMessage(unsupportedMessage: string | undefined) {
this.$unsupported.hidden =
unsupportedMessage === undefined || this.unsupportedDismissed;
this.$unsupportedMessage.textContent = unsupportedMessage ?? "";
}
send<Channel extends keyof RendererMessage>(
channel: Channel,
...args: Parameters<RendererMessage[Channel]>
): void {
ipcRenderer.sendTo(this.webContentsId, channel, ...args);
}
private registerListeners(): void {
const webContents = this.getWebContents();
if (shouldSilentWebview) {
webContents.setAudioMuted(true);
}
webContents.on("page-title-updated", (_event, title) => {
this.badgeCount = this.getBadgeCount(title);
this.props.onTitleChange();
});
this.$webview.addEventListener("did-navigate-in-page", () => {
this.canGoBackButton();
});
this.$webview.addEventListener("did-navigate", () => {
this.canGoBackButton();
});
webContents.on("page-favicon-updated", (_event, favicons) => {
// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like
// https://chat.zulip.org/static/images/favicon/favicon-pms.png
if (
favicons[0].indexOf("favicon-pms") > 0 &&
process.platform === "darwin"
) {
// This api is only supported on macOS
app.dock.setBadge("●");
// Bounce the dock
if (ConfigUtil.getConfigItem("dockBouncing", true)) {
app.dock.bounce();
}
}
});
webContents.addListener("context-menu", (event, menuParameters) => {
contextMenu(webContents, event, menuParameters);
});
this.$webview.addEventListener("dom-ready", () => {
this.loading = false;
this.props.switchLoading(false, this.props.url);
this.show();
});
webContents.on("did-fail-load", (_event, _errorCode, errorDescription) => {
const hasConnectivityError =
SystemUtil.connectivityError.includes(errorDescription);
if (hasConnectivityError) {
console.error("error", errorDescription);
if (!this.props.url.includes("network.html")) {
this.props.onNetworkError(this.props.index);
}
}
});
this.$webview.addEventListener("did-start-loading", () => {
this.props.switchLoading(true, this.props.url);
});
this.$webview.addEventListener("did-stop-loading", () => {
this.props.switchLoading(false, this.props.url);
});
this.$unsupportedDismiss.addEventListener("click", () => {
this.unsupportedDismissed = true;
this.$unsupported.hidden = true;
});
}
private getBadgeCount(title: string): number {
const messageCountInTitle = /^\((\d+)\)/.exec(title);
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
}
private show(): void {
// Do not show WebView if another tab was selected and this tab should be in background.
if (!this.props.isActive()) {
return;
}
// To show or hide the loading indicator in the active tab
this.$webviewsContainer.toggle("loaded", !this.loading);
this.$pane.classList.add("active");
this.focus();
this.props.onTitleChange();
// Injecting preload css in webview to override some css rules
(async () => this.getWebContents().insertCSS(preloadCss))();
// Get customCSS again from config util to avoid warning user again
const customCss = ConfigUtil.getConfigItem("customCSS", null);
this.customCss = customCss;
if (customCss) {
if (!fs.existsSync(customCss)) {
this.customCss = null;
ConfigUtil.setConfigItem("customCSS", null);
const errorMessage = "The custom css previously set is deleted!";
dialog.showErrorBox("custom css file deleted!", errorMessage);
return;
}
(async () =>
this.getWebContents().insertCSS(fs.readFileSync(customCss, "utf8")))();
}
}
}

View File

@@ -1,14 +1,15 @@
import {EventEmitter} from "node:events";
import {EventEmitter} from "events"; // eslint-disable-line unicorn/prefer-node-protocol
import type {ClipboardDecrypter} from "./clipboard-decrypter";
import {ClipboardDecrypterImpl} from "./clipboard-decrypter";
import type {NotificationData} from "./notification";
import {newNotification} from "./notification";
import {ipcRenderer} from "./typed-ipc-renderer";
import type {ClipboardDecrypter} from "./clipboard-decrypter.js";
import {ClipboardDecrypterImpl} from "./clipboard-decrypter.js";
import type {NotificationData} from "./notification/index.js";
import {newNotification} from "./notification/index.js";
import {ipcRenderer} from "./typed-ipc-renderer.js";
type ListenerType = (...args: any[]) => void;
export interface ElectronBridge {
/* eslint-disable @typescript-eslint/naming-convention */
export type ElectronBridge = {
send_event: (eventName: string | symbol, ...args: unknown[]) => boolean;
on_event: (eventName: string, listener: ListenerType) => void;
new_notification: (
@@ -21,7 +22,8 @@ export interface ElectronBridge {
get_send_notification_reply_message_supported: () => boolean;
set_send_notification_reply_message_supported: (value: boolean) => void;
decrypt_clipboard: (version: number) => ClipboardDecrypter;
}
};
/* eslint-enable @typescript-eslint/naming-convention */
let notificationReplySupported = false;
// Indicates if the user is idle or not
@@ -105,7 +107,7 @@ ipcRenderer.on("set-idle", () => {
// This follows node's idiomatic implementation of event
// emitters to make event handling more simpler instead of using
// functions zulip side will emit event using ElectronBrigde.send_event
// functions zulip side will emit event using ElectronBridge.send_event
// which is alias of .emit and on this side we can handle the data by adding
// a listener for the event.
export default electron_bridge;

View File

@@ -1,113 +0,0 @@
"use strict";
type ElectronBridge = import("./electron-bridge").ElectronBridge;
interface CompatElectronBridge extends ElectronBridge {
readonly idle_on_system: boolean;
readonly last_active_on_system: number;
send_notification_reply_message_supported: boolean;
}
(() => {
const zulipWindow = window as typeof window & {
electron_bridge: CompatElectronBridge;
raw_electron_bridge: ElectronBridge;
};
/* eslint-disable @typescript-eslint/naming-convention */
const electron_bridge: CompatElectronBridge = {
...zulipWindow.raw_electron_bridge,
get idle_on_system(): boolean {
return this.get_idle_on_system();
},
get last_active_on_system(): number {
return this.get_last_active_on_system();
},
get send_notification_reply_message_supported(): boolean {
return this.get_send_notification_reply_message_supported();
},
set send_notification_reply_message_supported(value: boolean) {
this.set_send_notification_reply_message_supported(value);
},
};
/* eslint-enable @typescript-eslint/naming-convention */
zulipWindow.electron_bridge = electron_bridge;
function attributeListener<T extends EventTarget>(
type: string,
): PropertyDescriptor {
const handlers = new WeakMap<T, (event: Event) => unknown>();
function listener(this: T, event: Event): void {
if (handlers.get(this)!.call(this, event) === false) {
event.preventDefault();
}
}
return {
configurable: true,
enumerable: true,
get(this: T) {
return handlers.get(this);
},
set(this: T, value: unknown) {
if (typeof value === "function") {
if (!handlers.has(this)) {
this.addEventListener(type, listener);
}
handlers.set(this, value as (event: Event) => unknown);
} else if (handlers.has(this)) {
this.removeEventListener(type, listener);
handlers.delete(this);
}
},
};
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const NativeNotification = Notification;
class InjectedNotification extends EventTarget {
static get permission(): NotificationPermission {
return NativeNotification.permission;
}
static async requestPermission(
callback?: NotificationPermissionCallback,
): Promise<NotificationPermission> {
if (callback) {
callback(await Promise.resolve(NativeNotification.permission));
}
return NativeNotification.permission;
}
constructor(title: string, options: NotificationOptions = {}) {
super();
Object.assign(
this,
electron_bridge.new_notification(
title,
options,
(type: string, eventInit: EventInit) =>
this.dispatchEvent(new Event(type, eventInit)),
),
);
}
}
Object.defineProperties(InjectedNotification.prototype, {
onclick: attributeListener("click"),
onclose: attributeListener("close"),
onerror: attributeListener("error"),
onshow: attributeListener("show"),
});
window.Notification = InjectedNotification as unknown as typeof Notification;
})();

View File

@@ -1,30 +1,33 @@
import {clipboard} from "electron/common";
import path from "node:path";
import process from "node:process";
import url from "node:url";
import {Menu, app, dialog, session} from "@electron/remote";
import * as remote from "@electron/remote";
import * as Sentry from "@sentry/electron";
import type {Config} from "../../common/config-util";
import * as ConfigUtil from "../../common/config-util";
import * as DNDUtil from "../../common/dnd-util";
import type {DndSettings} from "../../common/dnd-util";
import * as EnterpriseUtil from "../../common/enterprise-util";
import * as LinkUtil from "../../common/link-util";
import Logger from "../../common/logger-util";
import * as Messages from "../../common/messages";
import type {NavItem, ServerConf, TabData} from "../../common/types";
import type {Config} from "../../common/config-util.js";
import * as ConfigUtil from "../../common/config-util.js";
import * as DNDUtil from "../../common/dnd-util.js";
import type {DndSettings} from "../../common/dnd-util.js";
import * as EnterpriseUtil from "../../common/enterprise-util.js";
import * as LinkUtil from "../../common/link-util.js";
import Logger from "../../common/logger-util.js";
import * as Messages from "../../common/messages.js";
import {bundlePath, bundleUrl} from "../../common/paths.js";
import type {NavItem, ServerConf, TabData} from "../../common/types.js";
import defaultIcon from "../img/icon.png";
import FunctionalTab from "./components/functional-tab";
import ServerTab from "./components/server-tab";
import WebView from "./components/webview";
import {AboutView} from "./pages/about";
import {PreferenceView} from "./pages/preference/preference";
import {initializeTray} from "./tray";
import {ipcRenderer} from "./typed-ipc-renderer";
import * as DomainUtil from "./utils/domain-util";
import ReconnectUtil from "./utils/reconnect-util";
import FunctionalTab from "./components/functional-tab.js";
import ServerTab from "./components/server-tab.js";
import WebView from "./components/webview.js";
import {AboutView} from "./pages/about.js";
import {PreferenceView} from "./pages/preference/preference.js";
import {initializeTray} from "./tray.js";
import {ipcRenderer} from "./typed-ipc-renderer.js";
import * as DomainUtil from "./utils/domain-util.js";
import ReconnectUtil from "./utils/reconnect-util.js";
Sentry.init({});
@@ -44,12 +47,13 @@ const logger = new Logger({
file: "errors.log",
});
const rendererDirectory = path.resolve(__dirname, "..");
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
const rootWebContents = remote.getCurrentWebContents();
const dingSound = new Audio("../resources/sounds/ding.ogg");
const dingSound = new Audio(
new URL("resources/sounds/ding.ogg", bundleUrl).href,
);
export class ServerManagerView {
$addServerButton: HTMLButtonElement;
@@ -319,7 +323,15 @@ export class ServerManagerView {
const servers = DomainUtil.getDomains();
if (servers.length > 0) {
for (const [i, server] of servers.entries()) {
this.initServer(server, i);
const tab = this.initServer(server, i);
(async () => {
const serverConf = await DomainUtil.updateSavedServer(server.url, i);
tab.setName(serverConf.alias);
tab.setIcon(DomainUtil.iconAsUrl(serverConf.icon));
(await tab.webview).setUnsupportedMessage(
DomainUtil.getUnsupportedMessage(serverConf),
);
})();
}
// Open last active tab
@@ -328,11 +340,7 @@ export class ServerManagerView {
lastActiveTab = 0;
}
// `checkDomain()` and `webview.load()` for lastActiveTab before the others
await DomainUtil.updateSavedServer(
servers[lastActiveTab].url,
lastActiveTab,
);
// `webview.load()` for lastActiveTab before the others
await this.activateTab(lastActiveTab);
await Promise.all(
servers.map(async (server, i) => {
@@ -342,7 +350,6 @@ export class ServerManagerView {
return;
}
await DomainUtil.updateSavedServer(server.url, i);
const tab = this.tabs[i];
if (tab instanceof ServerTab) (await tab.webview).load();
}),
@@ -357,51 +364,54 @@ export class ServerManagerView {
}
}
initServer(server: ServerConf, index: number): void {
initServer(server: ServerConf, index: number): ServerTab {
const tabIndex = this.getTabIndex();
this.tabs.push(
new ServerTab({
role: "server",
icon: server.icon,
name: server.alias,
$root: this.$tabsContainer,
onClick: this.activateLastTab.bind(this, index),
const tab = new ServerTab({
role: "server",
icon: DomainUtil.iconAsUrl(server.icon),
name: server.alias,
$root: this.$tabsContainer,
onClick: this.activateLastTab.bind(this, index),
index,
tabIndex,
onHover: this.onHover.bind(this, index),
onHoverOut: this.onHoverOut.bind(this, index),
webview: WebView.create({
$root: this.$webviewsContainer,
rootWebContents,
index,
tabIndex,
onHover: this.onHover.bind(this, index),
onHoverOut: this.onHoverOut.bind(this, index),
webview: WebView.create({
$root: this.$webviewsContainer,
rootWebContents,
index,
tabIndex,
url: server.url,
role: "server",
hasPermission: (origin: string, permission: string) =>
origin === server.url && permission === "notifications",
isActive: () => index === this.activeTabIndex,
switchLoading: async (loading: boolean, url: string) => {
if (loading) {
this.loading.add(url);
} else {
this.loading.delete(url);
}
url: server.url,
role: "server",
hasPermission: (origin: string, permission: string) =>
origin === server.url &&
permission === "notifications" &&
ConfigUtil.getConfigItem("showNotification", true),
isActive: () => index === this.activeTabIndex,
switchLoading: async (loading: boolean, url: string) => {
if (loading) {
this.loading.add(url);
} else {
this.loading.delete(url);
}
const tab = this.tabs[this.activeTabIndex];
this.showLoading(
tab instanceof ServerTab &&
this.loading.has((await tab.webview).props.url),
);
},
onNetworkError: async (index: number) => {
await this.openNetworkTroubleshooting(index);
},
onTitleChange: this.updateBadge.bind(this),
preload: "js/preload.js",
}),
const tab = this.tabs[this.activeTabIndex];
this.showLoading(
tab instanceof ServerTab &&
this.loading.has((await tab.webview).props.url),
);
},
onNetworkError: async (index: number) => {
await this.openNetworkTroubleshooting(index);
},
onTitleChange: this.updateBadge.bind(this),
preload: url.pathToFileURL(path.join(bundlePath, "preload.js")).href,
unsupportedMessage: DomainUtil.getUnsupportedMessage(server),
}),
);
});
this.tabs.push(tab);
this.loading.add(server.url);
return tab;
}
initActions(): void {
@@ -415,7 +425,7 @@ export class ServerManagerView {
document.querySelectorAll(".server-icons");
for (const [index, $serverImg] of $serverImgs.entries()) {
this.addContextMenu($serverImg, index);
if ($serverImg.src.includes("img/icon.png")) {
if ($serverImg.src === defaultIcon) {
this.displayInitialCharLogo($serverImg, index);
}
@@ -489,7 +499,7 @@ export class ServerManagerView {
const realmName = $webview.getAttribute("name");
if (realmName === null) {
$img.src = "/img/icon.png";
$img.src = defaultIcon;
return;
}
@@ -543,7 +553,7 @@ export class ServerManagerView {
async openFunctionalTab(tabProps: {
name: string;
materialIcon: string;
makeView: () => Element;
makeView: () => Promise<Element>;
destroyView: () => void;
}): Promise<void> {
if (this.functionalTabs.has(tabProps.name)) {
@@ -555,7 +565,7 @@ export class ServerManagerView {
this.functionalTabs.set(tabProps.name, index);
const tabIndex = this.getTabIndex();
const $view = tabProps.makeView();
const $view = await tabProps.makeView();
this.$webviewsContainer.append($view);
this.tabs.push(
@@ -586,8 +596,8 @@ export class ServerManagerView {
await this.openFunctionalTab({
name: "Settings",
materialIcon: "settings",
makeView: () => {
this.preferenceView = new PreferenceView();
makeView: async () => {
this.preferenceView = await PreferenceView.create();
this.preferenceView.$view.classList.add("functional-view");
return this.preferenceView.$view;
},
@@ -605,8 +615,8 @@ export class ServerManagerView {
await this.openFunctionalTab({
name: "About",
materialIcon: "sentiment_very_satisfied",
makeView() {
aboutView = new AboutView();
async makeView() {
aboutView = await AboutView.create();
aboutView.$view.classList.add("functional-view");
return aboutView.$view;
},
@@ -624,7 +634,7 @@ export class ServerManagerView {
reconnectUtil.pollInternetAndReload();
await webview
.getWebContents()
.loadURL(`file://${rendererDirectory}/network.html`);
.loadURL(new URL("app/renderer/network.html", bundleUrl).href);
}
async activateLastTab(index: number): Promise<void> {
@@ -1039,14 +1049,9 @@ export class ServerManagerView {
"update-realm-name",
(event: Event, serverURL: string, realmName: string) => {
for (const [index, domain] of DomainUtil.getDomains().entries()) {
if (domain.url.includes(serverURL)) {
const serverTooltipSelector = ".tab .server-tooltip";
const serverTooltips = document.querySelectorAll(
serverTooltipSelector,
);
serverTooltips[index].textContent = realmName;
this.tabs[index].props.name = realmName;
if (domain.url === serverURL) {
const tab = this.tabs[index];
if (tab instanceof ServerTab) tab.setName(realmName);
domain.alias = realmName;
DomainUtil.updateDomain(index, domain);
// Update the realm name also on the Window menu
@@ -1064,15 +1069,12 @@ export class ServerManagerView {
async (event: Event, serverURL: string, iconURL: string) => {
await Promise.all(
DomainUtil.getDomains().map(async (domain, index) => {
if (domain.url.includes(serverURL)) {
const localIconUrl: string = await DomainUtil.saveServerIcon(
iconURL,
);
const serverImgsSelector = ".tab .server-icons";
const serverImgs: NodeListOf<HTMLImageElement> =
document.querySelectorAll(serverImgsSelector);
serverImgs[index].src = localIconUrl;
domain.icon = localIconUrl;
if (domain.url === serverURL) {
const localIconPath = await DomainUtil.saveServerIcon(iconURL);
const tab = this.tabs[index];
if (tab instanceof ServerTab)
tab.setIcon(DomainUtil.iconAsUrl(localIconPath));
domain.icon = localIconPath;
DomainUtil.updateDomain(index, domain);
}
}),
@@ -1110,7 +1112,7 @@ export class ServerManagerView {
ipcRenderer.on(
"render-taskbar-icon",
(event: Event, messageCount: number) => {
// Create a canvas from unread messagecounts
// Create a canvas from unread message counts
function createOverlayIcon(messageCount: number): HTMLCanvasElement {
const canvas = document.createElement("canvas");
canvas.height = 128;

View File

@@ -1,6 +1,6 @@
import {ipcRenderer} from "../typed-ipc-renderer";
import {ipcRenderer} from "../typed-ipc-renderer.js";
export interface NotificationData {
export type NotificationData = {
close: () => void;
title: string;
dir: NotificationDirection;
@@ -9,7 +9,7 @@ export interface NotificationData {
tag: string;
icon: string;
data: unknown;
}
};
export function newNotification(
title: string,

View File

@@ -1,41 +1,21 @@
import {app} from "@electron/remote";
import {html} from "../../../common/html";
import {bundleUrl} from "../../../common/paths.js";
export class AboutView {
static async create(): Promise<AboutView> {
return new AboutView(
await (await fetch(new URL("app/renderer/about.html", bundleUrl))).text(),
);
}
readonly $view: HTMLElement;
constructor() {
private constructor(templateHtml: string) {
this.$view = document.createElement("div");
const $shadow = this.$view.attachShadow({mode: "open"});
$shadow.innerHTML = html`
<link rel="stylesheet" href="css/about.css" />
<!-- Initially hidden to prevent FOUC -->
<div class="about" hidden>
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version">v${app.getVersion()}</p>
<div class="maintenance-info">
<p class="detail maintainer">
Maintained by
<a
href="https://zulip.com"
target="_blank"
rel="noopener noreferrer"
>Zulip</a
>
</p>
<p class="detail license">
Available under the
<a
href="https://github.com/zulip/zulip-desktop/blob/main/LICENSE"
target="_blank"
rel="noopener noreferrer"
>Apache 2.0 License</a
>
</p>
</div>
</div>
`.html;
$shadow.innerHTML = templateHtml;
$shadow.querySelector("#version")!.textContent = `v${app.getVersion()}`;
}
destroy() {

View File

@@ -1,4 +1,4 @@
import {ipcRenderer} from "../typed-ipc-renderer";
import {ipcRenderer} from "../typed-ipc-renderer.js";
export function init(
$reconnectButton: Element,

View File

@@ -1,14 +1,14 @@
import type {Html} from "../../../../common/html";
import {html} from "../../../../common/html";
import {generateNodeFromHtml} from "../../components/base";
import {ipcRenderer} from "../../typed-ipc-renderer";
import type {Html} from "../../../../common/html.js";
import {html} from "../../../../common/html.js";
import {generateNodeFromHtml} from "../../components/base.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
interface BaseSectionProps {
type BaseSectionProps = {
$element: HTMLElement;
disabled?: boolean;
value: boolean;
clickHandler: () => void;
}
};
export function generateSettingOption(props: BaseSectionProps): void {
const {$element, disabled, value, clickHandler} = props;

View File

@@ -1,15 +1,15 @@
import {html} from "../../../../common/html";
import * as t from "../../../../common/translation-util";
import {ipcRenderer} from "../../typed-ipc-renderer";
import * as DomainUtil from "../../utils/domain-util";
import {html} from "../../../../common/html.js";
import * as t from "../../../../common/translation-util.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
import * as DomainUtil from "../../utils/domain-util.js";
import {reloadApp} from "./base-section";
import {initFindAccounts} from "./find-accounts";
import {initServerInfoForm} from "./server-info-form";
import {reloadApp} from "./base-section.js";
import {initFindAccounts} from "./find-accounts.js";
import {initServerInfoForm} from "./server-info-form.js";
interface ConnectedOrgSectionProps {
type ConnectedOrgSectionProps = {
$root: Element;
}
};
export function initConnectedOrgSection({
$root,
@@ -21,7 +21,7 @@ export function initConnectedOrgSection({
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">${t.__("Connected organizations")}</div>
<div class="title" id="existing-servers">
${t.__("All the connected orgnizations will appear here.")}
${t.__("All the connected organizations will appear here.")}
</div>
<div id="server-info-container"></div>
<div id="new-org-button">
@@ -42,7 +42,9 @@ export function initConnectedOrgSection({
"#find-accounts-container",
)!;
const noServerText = t.__("All the connected orgnizations will appear here");
const noServerText = t.__(
"All the connected organizations will appear here.",
);
// Show noServerText if no servers are there otherwise hide it
$existingServers.textContent = servers.length === 0 ? noServerText : "";

View File

@@ -1,11 +1,11 @@
import {html} from "../../../../common/html";
import * as LinkUtil from "../../../../common/link-util";
import * as t from "../../../../common/translation-util";
import {generateNodeFromHtml} from "../../components/base";
import {html} from "../../../../common/html.js";
import * as LinkUtil from "../../../../common/link-util.js";
import * as t from "../../../../common/translation-util.js";
import {generateNodeFromHtml} from "../../components/base.js";
interface FindAccountsProps {
type FindAccountsProps = {
$root: Element;
}
};
async function findAccounts(url: string): Promise<void> {
if (!url) {

View File

@@ -7,22 +7,22 @@ import * as remote from "@electron/remote";
import {app, dialog, session} from "@electron/remote";
import Tagify from "@yaireo/tagify";
import ISO6391 from "iso-639-1";
import * as z from "zod";
import {z} from "zod";
import * as ConfigUtil from "../../../../common/config-util";
import * as EnterpriseUtil from "../../../../common/enterprise-util";
import {html} from "../../../../common/html";
import * as t from "../../../../common/translation-util";
import supportedLocales from "../../../../translations/supported-locales.json";
import {ipcRenderer} from "../../typed-ipc-renderer";
import supportedLocales from "../../../../../public/translations/supported-locales.json";
import * as ConfigUtil from "../../../../common/config-util.js";
import * as EnterpriseUtil from "../../../../common/enterprise-util.js";
import {html} from "../../../../common/html.js";
import * as t from "../../../../common/translation-util.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
import {generateSelectHtml, generateSettingOption} from "./base-section";
import {generateSelectHtml, generateSettingOption} from "./base-section.js";
const currentBrowserWindow = remote.getCurrentWindow();
interface GeneralSectionProps {
type GeneralSectionProps = {
$root: Element;
}
};
export function initGeneralSection({$root}: GeneralSectionProps): void {
$root.innerHTML = html`
@@ -592,7 +592,7 @@ export function initGeneralSection({$root}: GeneralSectionProps): void {
}
function initSpellChecker(): void {
// The elctron API is a no-op on macOS and macOS default spellchecker is used.
// The Electron API is a no-op on macOS and macOS default spellchecker is used.
if (process.platform === "darwin") {
const note: HTMLElement = $root.querySelector("#note")!;
note.append(t.__("On macOS, the OS spellchecker is used."));
@@ -610,13 +610,15 @@ export function initGeneralSection({$root}: GeneralSectionProps): void {
const spellDiv: HTMLElement = $root.querySelector("#spellcheck-langs")!;
spellDiv.innerHTML += html`
<div class="setting-description">${t.__("Spellchecker Languages")}</div>
<input name="spellcheck" placeholder="Enter Languages" />
<div id="spellcheck-langs-value">
<input name="spellcheck" placeholder="Enter Languages" />
</div>
`.html;
const availableLanguages = session.fromPartition(
"persist:webviewsession",
).availableSpellCheckerLanguages;
let languagePairs: Map<string, string> = new Map();
let languagePairs = new Map<string, string>();
for (const l of availableLanguages) {
if (ISO6391.validate(l)) {
languagePairs.set(ISO6391.getName(l), l);
@@ -652,8 +654,20 @@ export function initGeneralSection({$root}: GeneralSectionProps): void {
maxItems: Number.POSITIVE_INFINITY,
closeOnSelect: false,
highlightFirst: true,
position: "manual",
classname: "settings-tagify-dropdown",
},
});
tagify.DOM.input.addEventListener("focus", () => {
tagify.dropdown.show();
$root
.querySelector("#spellcheck-langs-value")!
.append(tagify.DOM.dropdown);
});
tagify.DOM.input.addEventListener("blur", () => {
tagify.dropdown.hide(true);
tagify.DOM.dropdown.remove();
});
const configuredLanguages: string[] = (
ConfigUtil.getConfigItem("spellcheckerLanguages", null) ?? []

View File

@@ -1,20 +1,18 @@
import type {Html} from "../../../../common/html";
import {html} from "../../../../common/html";
import * as t from "../../../../common/translation-util";
import type {NavItem} from "../../../../common/types";
import {generateNodeFromHtml} from "../../components/base";
import type {Html} from "../../../../common/html.js";
import {html} from "../../../../common/html.js";
import * as t from "../../../../common/translation-util.js";
import type {NavItem} from "../../../../common/types.js";
import {generateNodeFromHtml} from "../../components/base.js";
interface PreferenceNavProps {
type PreferenceNavProps = {
$root: Element;
onItemSelected: (navItem: NavItem) => void;
}
};
export default class PreferenceNav {
props: PreferenceNavProps;
navItems: NavItem[];
$el: Element;
constructor(props: PreferenceNavProps) {
this.props = props;
constructor(private readonly props: PreferenceNavProps) {
this.navItems = [
"General",
"Network",

View File

@@ -1,13 +1,13 @@
import * as ConfigUtil from "../../../../common/config-util";
import {html} from "../../../../common/html";
import * as t from "../../../../common/translation-util";
import {ipcRenderer} from "../../typed-ipc-renderer";
import * as ConfigUtil from "../../../../common/config-util.js";
import {html} from "../../../../common/html.js";
import * as t from "../../../../common/translation-util.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
import {generateSettingOption} from "./base-section";
import {generateSettingOption} from "./base-section.js";
interface NetworkSectionProps {
type NetworkSectionProps = {
$root: Element;
}
};
export function initNetworkSection({$root}: NetworkSectionProps): void {
$root.innerHTML = html`

View File

@@ -1,16 +1,16 @@
import {dialog} from "@electron/remote";
import {html} from "../../../../common/html";
import * as LinkUtil from "../../../../common/link-util";
import * as t from "../../../../common/translation-util";
import {generateNodeFromHtml} from "../../components/base";
import {ipcRenderer} from "../../typed-ipc-renderer";
import * as DomainUtil from "../../utils/domain-util";
import {html} from "../../../../common/html.js";
import * as LinkUtil from "../../../../common/link-util.js";
import * as t from "../../../../common/translation-util.js";
import {generateNodeFromHtml} from "../../components/base.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
import * as DomainUtil from "../../utils/domain-util.js";
interface NewServerFormProps {
type NewServerFormProps = {
$root: Element;
onChange: () => void;
}
};
export function initNewServerForm({$root, onChange}: NewServerFormProps): void {
const $newServerForm = generateNodeFromHtml(html`

View File

@@ -1,46 +1,36 @@
import process from "node:process";
import type {DndSettings} from "../../../../common/dnd-util";
import {html} from "../../../../common/html";
import type {NavItem} from "../../../../common/types";
import {ipcRenderer} from "../../typed-ipc-renderer";
import type {DndSettings} from "../../../../common/dnd-util.js";
import {bundleUrl} from "../../../../common/paths.js";
import type {NavItem} from "../../../../common/types.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
import {initConnectedOrgSection} from "./connected-org-section";
import {initGeneralSection} from "./general-section";
import Nav from "./nav";
import {initNetworkSection} from "./network-section";
import {initServersSection} from "./servers-section";
import {initShortcutsSection} from "./shortcuts-section";
import {initConnectedOrgSection} from "./connected-org-section.js";
import {initGeneralSection} from "./general-section.js";
import Nav from "./nav.js";
import {initNetworkSection} from "./network-section.js";
import {initServersSection} from "./servers-section.js";
import {initShortcutsSection} from "./shortcuts-section.js";
export class PreferenceView {
static async create(): Promise<PreferenceView> {
return new PreferenceView(
await (
await fetch(new URL("app/renderer/preference.html", bundleUrl))
).text(),
);
}
readonly $view: HTMLElement;
private readonly $shadow: ShadowRoot;
private readonly $settingsContainer: Element;
private readonly nav: Nav;
private navItem: NavItem = "General";
constructor() {
private constructor(templateHtml: string) {
this.$view = document.createElement("div");
this.$shadow = this.$view.attachShadow({mode: "open"});
this.$shadow.innerHTML = html`
<link
rel="stylesheet"
href="${require.resolve("../../../css/fonts.css")}"
/>
<link
rel="stylesheet"
href="${require.resolve("../../../css/preference.css")}"
/>
<link
rel="stylesheet"
href="${require.resolve("@yaireo/tagify/dist/tagify.css")}"
/>
<!-- Initially hidden to prevent FOUC -->
<div id="content" hidden>
<div id="sidebar"></div>
<div id="settings-container"></div>
</div>
`.html;
this.$shadow.innerHTML = templateHtml;
const $sidebarContainer = this.$shadow.querySelector("#sidebar")!;
this.$settingsContainer = this.$shadow.querySelector(
@@ -63,29 +53,33 @@ export class PreferenceView {
this.navItem = navItem;
this.nav.select(navItem);
switch (navItem) {
case "AddServer":
case "AddServer": {
initServersSection({
$root: this.$settingsContainer,
});
break;
}
case "General":
case "General": {
initGeneralSection({
$root: this.$settingsContainer,
});
break;
}
case "Organizations":
case "Organizations": {
initConnectedOrgSection({
$root: this.$settingsContainer,
});
break;
}
case "Network":
case "Network": {
initNetworkSection({
$root: this.$settingsContainer,
});
break;
}
case "Shortcuts": {
initShortcutsSection({
@@ -94,8 +88,9 @@ export class PreferenceView {
break;
}
default:
default: {
((n: never) => n)(navItem);
}
}
window.location.hash = `#${navItem}`;

View File

@@ -1,25 +1,28 @@
import {dialog} from "@electron/remote";
import {html} from "../../../../common/html";
import * as Messages from "../../../../common/messages";
import * as t from "../../../../common/translation-util";
import type {ServerConf} from "../../../../common/types";
import {generateNodeFromHtml} from "../../components/base";
import {ipcRenderer} from "../../typed-ipc-renderer";
import * as DomainUtil from "../../utils/domain-util";
import {html} from "../../../../common/html.js";
import * as Messages from "../../../../common/messages.js";
import * as t from "../../../../common/translation-util.js";
import type {ServerConf} from "../../../../common/types.js";
import {generateNodeFromHtml} from "../../components/base.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
import * as DomainUtil from "../../utils/domain-util.js";
interface ServerInfoFormProps {
type ServerInfoFormProps = {
$root: Element;
server: ServerConf;
index: number;
onChange: () => void;
}
};
export function initServerInfoForm(props: ServerInfoFormProps): void {
const $serverInfoForm = generateNodeFromHtml(html`
<div class="settings-card">
<div class="server-info-left">
<img class="server-info-icon" src="${props.server.icon}" />
<img
class="server-info-icon"
src="${DomainUtil.iconAsUrl(props.server.icon)}"
/>
<div class="server-info-row">
<span class="server-info-alias">${props.server.alias}</span>
<i class="material-icons open-tab-button">open_in_new</i>

View File

@@ -1,12 +1,12 @@
import {html} from "../../../../common/html";
import * as t from "../../../../common/translation-util";
import {html} from "../../../../common/html.js";
import * as t from "../../../../common/translation-util.js";
import {reloadApp} from "./base-section";
import {initNewServerForm} from "./new-server-form";
import {reloadApp} from "./base-section.js";
import {initNewServerForm} from "./new-server-form.js";
interface ServersSectionProps {
type ServersSectionProps = {
$root: Element;
}
};
export function initServersSection({$root}: ServersSectionProps): void {
$root.innerHTML = html`

View File

@@ -1,12 +1,12 @@
import process from "node:process";
import {html} from "../../../../common/html";
import * as LinkUtil from "../../../../common/link-util";
import * as t from "../../../../common/translation-util";
import {html} from "../../../../common/html.js";
import * as LinkUtil from "../../../../common/link-util.js";
import * as t from "../../../../common/translation-util.js";
interface ShortcutsSectionProps {
type ShortcutsSectionProps = {
$root: Element;
}
};
// eslint-disable-next-line complexity
export function initShortcutsSection({$root}: ShortcutsSectionProps): void {

View File

@@ -1,68 +1,21 @@
import {contextBridge, webFrame} from "electron/renderer";
import fs from "node:fs";
import {contextBridge} from "electron/renderer";
import electron_bridge, {bridgeEvents} from "./electron-bridge";
import * as NetworkError from "./pages/network";
import {ipcRenderer} from "./typed-ipc-renderer";
import electron_bridge, {bridgeEvents} from "./electron-bridge.js";
import * as NetworkError from "./pages/network.js";
import {ipcRenderer} from "./typed-ipc-renderer.js";
contextBridge.exposeInMainWorld("raw_electron_bridge", electron_bridge);
contextBridge.exposeInMainWorld("electron_bridge", electron_bridge);
ipcRenderer.on("logout", () => {
if (bridgeEvents.emit("logout")) {
return;
}
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector(".dropdown-toggle")!;
dropdown.click();
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll(
".dropdown-menu li:last-child a",
);
nodes[nodes.length - 1].click();
bridgeEvents.emit("logout");
});
ipcRenderer.on("show-keyboard-shortcuts", () => {
if (bridgeEvents.emit("show-keyboard-shortcuts")) {
return;
}
// Create the menu for the below
const node: HTMLElement = document.querySelector(
"a[data-overlay-trigger=keyboard-shortcuts]",
)!;
// Additional check
if (node.textContent!.trim().toLowerCase() === "keyboard shortcuts (?)") {
node.click();
} else {
// Atleast click the dropdown
const dropdown: HTMLElement = document.querySelector(".dropdown-toggle")!;
dropdown.click();
}
bridgeEvents.emit("show-keyboard-shortcuts");
});
ipcRenderer.on("show-notification-settings", () => {
if (bridgeEvents.emit("show-notification-settings")) {
return;
}
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector(".dropdown-toggle")!;
dropdown.click();
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll(
".dropdown-menu li a",
);
nodes[2].click();
const notificationItem: NodeListOf<HTMLElement> = document.querySelectorAll(
".normal-settings-list li div",
);
// Wait until the notification dom element shows up
setTimeout(() => {
notificationItem[2].click();
}, 100);
bridgeEvents.emit("show-notification-settings");
});
window.addEventListener("load", () => {
@@ -74,8 +27,3 @@ window.addEventListener("load", () => {
const $settingsButton = document.querySelector("#settings")!;
NetworkError.init($reconnectButton, $settingsButton);
});
(async () =>
webFrame.executeJavaScript(
fs.readFileSync(require.resolve("./injected"), "utf8"),
))();

View File

@@ -6,19 +6,16 @@ import process from "node:process";
import {BrowserWindow, Menu, Tray} from "@electron/remote";
import * as ConfigUtil from "../../common/config-util";
import type {RendererMessage} from "../../common/typed-ipc";
import * as ConfigUtil from "../../common/config-util.js";
import {publicPath} from "../../common/paths.js";
import type {RendererMessage} from "../../common/typed-ipc.js";
import type {ServerManagerView} from "./main";
import {ipcRenderer} from "./typed-ipc-renderer";
import type {ServerManagerView} from "./main.js";
import {ipcRenderer} from "./typed-ipc-renderer.js";
let tray: ElectronTray | null = null;
const iconDir = "../../resources/tray";
const traySuffix = "tray";
const appIcon = path.join(__dirname, iconDir, traySuffix);
const appIcon = path.join(publicPath, "resources/tray/tray");
const iconPath = (): string => {
if (process.platform === "linux") {
@@ -36,14 +33,21 @@ let unread = 0;
const trayIconSize = (): number => {
switch (process.platform) {
case "darwin":
case "darwin": {
return 20;
case "win32":
}
case "win32": {
return 100;
case "linux":
}
case "linux": {
return 100;
default:
}
default: {
return 80;
}
}
};
@@ -236,5 +240,3 @@ export function initializeTray(serverManagerView: ServerManagerView) {
createTray();
}
}
export {};

View File

@@ -7,7 +7,7 @@ import type {
MainCall,
MainMessage,
RendererMessage,
} from "../../common/typed-ipc";
} from "../../common/typed-ipc.js";
type RendererListener<Channel extends keyof RendererMessage> =
RendererMessage[Channel] extends (...args: infer Args) => void

View File

@@ -5,24 +5,30 @@ import {app, dialog} from "@electron/remote";
import * as Sentry from "@sentry/electron";
import {JsonDB} from "node-json-db";
import {DataError} from "node-json-db/dist/lib/Errors";
import * as z from "zod";
import {z} from "zod";
import * as EnterpriseUtil from "../../../common/enterprise-util";
import Logger from "../../../common/logger-util";
import * as Messages from "../../../common/messages";
import type {ServerConf} from "../../../common/types";
import {ipcRenderer} from "../typed-ipc-renderer";
import * as EnterpriseUtil from "../../../common/enterprise-util.js";
import Logger from "../../../common/logger-util.js";
import * as Messages from "../../../common/messages.js";
import * as t from "../../../common/translation-util.js";
import type {ServerConf} from "../../../common/types.js";
import defaultIcon from "../../img/icon.png";
import {ipcRenderer} from "../typed-ipc-renderer.js";
const logger = new Logger({
file: "domain-util.log",
});
const defaultIconUrl = "../renderer/img/icon.png";
// For historical reasons, we store this string in domain.json to denote a
// missing icon; it does not change with the actual icon location.
export const defaultIconSentinel = "../renderer/img/icon.png";
const serverConfSchema = z.object({
url: z.string(),
url: z.string().url(),
alias: z.string(),
icon: z.string(),
zulipVersion: z.string().default("unknown"),
zulipFeatureLevel: z.number().default(0),
});
let db!: JsonDB;
@@ -78,7 +84,7 @@ export async function addDomain(server: {
db.push("/domains[]", server, true);
reloadDb();
} else {
server.icon = defaultIconUrl;
server.icon = defaultIconSentinel;
serverConfSchema.parse(server);
db.push("/domains[]", server, true);
reloadDb();
@@ -129,27 +135,34 @@ async function getServerSettings(domain: string): Promise<ServerConf> {
}
export async function saveServerIcon(iconURL: string): Promise<string> {
return ipcRenderer.invoke("save-server-icon", iconURL);
return (
(await ipcRenderer.invoke("save-server-icon", iconURL)) ??
defaultIconSentinel
);
}
export async function updateSavedServer(
url: string,
index: number,
): Promise<void> {
): Promise<ServerConf> {
// Does not promise successful update
const oldIcon = getDomain(index).icon;
const serverConf = getDomain(index);
const oldIcon = serverConf.icon;
try {
const newServerConf = await checkDomain(url, true);
const localIconUrl = await saveServerIcon(newServerConf.icon);
if (!oldIcon || localIconUrl !== "../renderer/img/icon.png") {
if (!oldIcon || localIconUrl !== defaultIconSentinel) {
newServerConf.icon = localIconUrl;
updateDomain(index, newServerConf);
reloadDb();
}
return newServerConf;
} catch (error: unknown) {
logger.log("Could not update server icon.");
logger.log(error);
Sentry.captureException(error);
return serverConf;
}
}
@@ -189,3 +202,28 @@ export function formatUrl(domain: string): string {
return `https://${domain}`;
}
export function getUnsupportedMessage(server: ServerConf): string | undefined {
if (server.zulipFeatureLevel < 65 /* Zulip Server 4.0 */) {
const realm = new URL(server.url).hostname;
return t.__(
"{{{server}}} runs an outdated Zulip Server version {{{version}}}. It may not fully work in this app.",
{server: realm, version: server.zulipVersion},
);
}
return undefined;
}
export function iconAsUrl(iconPath: string): string {
if (iconPath === defaultIconSentinel) return defaultIcon;
try {
return `data:application/octet-stream;base64,${fs.readFileSync(
iconPath,
"base64",
)}`;
} catch {
return defaultIcon;
}
}

View File

@@ -1,22 +1,20 @@
import * as backoff from "backoff";
import {html} from "../../../common/html";
import Logger from "../../../common/logger-util";
import type WebView from "../components/webview";
import {ipcRenderer} from "../typed-ipc-renderer";
import {html} from "../../../common/html.js";
import Logger from "../../../common/logger-util.js";
import type WebView from "../components/webview.js";
import {ipcRenderer} from "../typed-ipc-renderer.js";
const logger = new Logger({
file: "domain-util.log",
});
export default class ReconnectUtil {
webview: WebView;
url: string;
alreadyReloaded: boolean;
fibonacciBackoff: backoff.Backoff;
constructor(webview: WebView) {
this.webview = webview;
this.url = webview.props.url;
this.alreadyReloaded = false;
this.fibonacciBackoff = backoff.fibonacci({

View File

@@ -1,4 +1,4 @@
import {ipcRenderer} from "../typed-ipc-renderer";
import {ipcRenderer} from "../typed-ipc-renderer.js";
export const connectivityError: string[] = [
"ERR_INTERNET_DISCONNECTED",

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width" />
<title>Zulip</title>
<link rel="stylesheet" href="css/fonts.css" />
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen" />
<link rel="stylesheet" href="css/main.css" />
</head>
<body>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="UTF-8" />
<link rel="stylesheet" href="css/fonts.css" />
<link rel="stylesheet" href="css/preference.css" />
<!-- Initially hidden to prevent FOUC -->
<div id="content" hidden>
<div id="sidebar"></div>
<div id="settings-container"></div>
</div>

View File

@@ -1,134 +0,0 @@
{
"About Zulip": "Tietoa Zulipista",
"Actual Size": "Varsinainen koko",
"Add Custom Certificates": "Lisää omia sertifikaatteja",
"Add Organization": "Lisää organisaatio",
"Add a Zulip organization": "Lisää Zulip-organisaatio",
"Add custom CSS": "Lisää oma CSS",
"Advanced": "Edistynyt",
"All the connected organizations will appear here": "All the connected organizations will appear here",
"Always start minimized": "Aloita aina pienennettynä",
"App Updates": "Sovellspäivitykset",
"Appearance": "Ulkonäkö",
"Application Shortcuts": "Sovellusoikotiet",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "Back",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"Change": "Muuta",
"Check for Updates": "Check for Updates",
"Close": "Sulje",
"Connect": "Yhdistä",
"Connect to another organization": "Connect to another organization",
"Connected organizations": "Connected organizations",
"Copy": "Kopioi",
"Copy Zulip URL": "Kopioi Zulip-URL",
"Create a new organization": "Luo uusi organisaatio",
"Cut": "Leikkaa",
"Default download location": "Default download location",
"Delete": "Poista",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Desktop Settings",
"Disconnect": "Katkaise",
"Download App Logs": "Download App Logs",
"Edit": "Muokkaa",
"Edit Shortcuts": "Edit Shortcuts",
"Enable auto updates": "Enable auto updates",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Factory Reset": "Factory Reset",
"File": "Tiedosto",
"Find accounts": "Löydä tilit",
"Find accounts by email": "Find accounts by email",
"Flash taskbar on new message": "Flash taskbar on new message",
"Forward": "Forward",
"Functionality": "Functionality",
"General": "General",
"Get beta updates": "Get beta updates",
"Hard Reload": "Hard Reload",
"Help": "Ohje",
"Help Center": "Tukikeskus",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Log Out": "Log Out",
"Log Out of Organization": "Log Out of Organization",
"Manual proxy configuration": "Manual proxy configuration",
"Minimize": "Minimize",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "EI",
"Network": "Network",
"OR": "TAI",
"Organization URL": "Organisaation URL",
"Organizations": "Organisaatiot",
"Paste": "Liitä",
"Paste and Match Style": "Liitä ja täsmää tyylit",
"Proxy": "Välipalvelin",
"Proxy bypass rules": "Proxy bypass rules",
"Proxy rules": "Proxy rules",
"Quit": "Lopeta",
"Quit Zulip": "Lopeta Zulip",
"Redo": "Toista",
"Release Notes": "Release Notes",
"Reload": "Lataa uudelleen",
"Report an Issue": "Report an Issue",
"Save": "Tallenna",
"Select All": "Valitse kaikki",
"Settings": "Asetukset",
"Shortcuts": "Oikopolut",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Vinkki",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Työkalut",
"Undo": "Peru",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "Näytä",
"View Shortcuts": "View Shortcuts",
"Window": "Ikkuna",
"Window Shortcuts": "Window Shortcuts",
"YES": "KYLLÄ",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
}

View File

@@ -1,134 +0,0 @@
{
"About Zulip": "О Зулипу",
"Actual Size": "Стварна величина",
"Add Custom Certificates": "Додајте прилагођене цертификате",
"Add Organization": "Додај организацију",
"Add a Zulip organization": "Додајте Зулип организацију",
"Add custom CSS": "Додајте прилагођени ЦСС",
"Advanced": "Напредно",
"All the connected organizations will appear here": "Овде ће се појавити све повезане организације",
"Always start minimized": "Увек започните минимизирано",
"App Updates": "Апп Упдатес",
"Appearance": "Изглед",
"Application Shortcuts": "Пречице за апликације",
"Are you sure you want to disconnect this organization?": "Јесте ли сигурни да желите прекинути везу с овом организацијом?",
"Auto hide Menu bar": "Ауто хиде Мену бар",
"Auto hide menu bar (Press Alt key to display)": "Аутоматско скривање траке менија (притисните тастер Алт да бисте приказали)",
"Back": "Назад",
"Bounce dock on new private message": "Одскочите у нову приватну поруку",
"Certificate file": "Датотека сертификата",
"Change": "Цханге",
"Check for Updates": "Провери ажурирања",
"Close": "Близу",
"Connect": "Повежи",
"Connect to another organization": "Повежите се са другом организацијом",
"Connected organizations": "Повезане организације",
"Copy": "Копирај",
"Copy Zulip URL": "Цопи Зулип УРЛ",
"Create a new organization": "Направите нову организацију",
"Cut": "Цут",
"Default download location": "Дефаулт довнлоад лоцатион",
"Delete": "Обриши",
"Desktop App Settings": "Подешавања апликације за десктоп рачунаре",
"Desktop Notifications": "Обавештења о радној површини",
"Desktop Settings": "Десктоп Сеттингс",
"Disconnect": "Дисцоннецт",
"Download App Logs": "Довнлоад Апп Логс",
"Edit": "Уредити",
"Edit Shortcuts": "Уреди пречице",
"Enable auto updates": "Омогући аутоматско ажурирање",
"Enable error reporting (requires restart)": "Омогући извештавање о грешкама (захтева поновно покретање)",
"Enable spellchecker (requires restart)": "Омогући провјеру правописа (захтијева поновно покретање)",
"Factory Reset": "Фацтори Ресет",
"File": "Филе",
"Find accounts": "Нађи рачуне",
"Find accounts by email": "Пронађите рачуне путем е-поште",
"Flash taskbar on new message": "Фласх трака задатака у новој поруци",
"Forward": "Напријед",
"Functionality": "Функционалност",
"General": "Генерал",
"Get beta updates": "Набавите бета ажурирања",
"Hard Reload": "Хард Релоад",
"Help": "Помоћ",
"Help Center": "Центар за помоћ",
"History": "Хистори",
"History Shortcuts": "Историјске пречице",
"Keyboard Shortcuts": "Пречице на тастатури",
"Log Out": "Одјавити се",
"Log Out of Organization": "Одјавите се из организације",
"Manual proxy configuration": "Мануал проки цонфигуратион",
"Minimize": "Минимизе",
"Mute all sounds from Zulip": "Искључите све звукове из Зулипа",
"NO": "НЕ",
"Network": "Мрежа",
"OR": "ОР",
"Organization URL": "УРЛ организације",
"Organizations": "Организације",
"Paste": "Пасте",
"Paste and Match Style": "Залепите и подесите стил",
"Proxy": "Заступник",
"Proxy bypass rules": "Проки бипасс правила",
"Proxy rules": "Проки рулес",
"Quit": "Одустати",
"Quit Zulip": "Куит Зулип",
"Redo": "Редо",
"Release Notes": "Релеасе Нотес",
"Reload": "Освежи",
"Report an Issue": "Пријавите проблем",
"Save": "сачувати",
"Select All": "Изабери све",
"Settings": "Подешавања",
"Shortcuts": "Пречице",
"Show App Logs": "Прикажи дневнике апликација",
"Show app icon in system tray": "Покажи икону апликације у системској палети",
"Show app unread badge": "Покажи непрочитану значку апликације",
"Show desktop notifications": "Прикажи обавештења радне површине",
"Show downloaded files in file manager": "Прикажи преузете датотеке у управитељу датотека",
"Show sidebar": "Схов сидебар",
"Start app at login": "Покрените апликацију приликом пријављивања",
"Switch to Next Organization": "Пребаци се на следећу организацију",
"Switch to Previous Organization": "Пребаци се на претходну организацију",
"These desktop app shortcuts extend the Zulip webapp's": "Пречице за десктоп апликације проширују Зулип вебаппове",
"This will delete all application data including all added accounts and preferences": "Ово ће избрисати све податке о апликацији, укључујући све додатне налоге и поставке",
"Tip": "Савет",
"Toggle DevTools for Active Tab": "Пребаци ДевТоолс за Ацтиве Таб",
"Toggle DevTools for Zulip App": "Пребаци ДевТоолс за Зулип Апп",
"Toggle Do Not Disturb": "Тоггле До Нот Дистурб",
"Toggle Full Screen": "Тоггле Фулл Сцреен",
"Toggle Sidebar": "Тоггле Сидебар",
"Toggle Tray Icon": "Тоггле Траи Ицон",
"Tools": "Алати",
"Undo": "Ундо",
"Upload": "Отпремити",
"Use system proxy settings (requires restart)": "Користи поставке системског прокија (потребно је поново покренути)",
"View": "Поглед",
"View Shortcuts": "Прикажи пречице",
"Window": "Прозор",
"Window Shortcuts": "Пречице за прозор",
"YES": "ДА",
"Zoom In": "Увеличати",
"Zoom Out": "Зоом Оут",
"Zulip Help": "Зулип Хелп",
"keyboard shortcuts": "пречице на тастатури",
"script": "скрипта",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
}

View File

@@ -1,134 +0,0 @@
{
"About Zulip": "Giới thiệu",
"Actual Size": "Kích thước thực",
"Add Custom Certificates": "Thêm chứng chỉ tự tùy chỉnh",
"Add Organization": "Thêm nhóm",
"Add a Zulip organization": "Thêm nhóm Zulip",
"Add custom CSS": "Thêm chỉnh sửa CSS",
"Advanced": "Nâng cao",
"All the connected organizations will appear here": "Tất cả các nhóm đã kết nối sẽ hiển thị tại đây",
"Always start minimized": "Luôn thu nhỏ",
"App Updates": "Cập nhật",
"Appearance": "Giao diện",
"Application Shortcuts": "Phím tắt",
"Are you sure you want to disconnect this organization?": "Bạn có chắc muốn ngừng kết nối với nhóm này?",
"Auto hide Menu bar": "Tự động ẩn thanh công cụ",
"Auto hide menu bar (Press Alt key to display)": "Tự động ẩn thanh công cụ (Ấn phím Alt để hiển thị)",
"Back": "Quay lại",
"Bounce dock on new private message": "Bounce dock trên tin nhắn mới",
"Certificate file": "Giấy chứng nhận",
"Change": "Thay đổi",
"Check for Updates": "Kiểm tra cập nhật",
"Close": "Tắt",
"Connect": "Kết nối",
"Connect to another organization": "Kết nối với tổ chức khác",
"Connected organizations": "Tổ chức kết nối",
"Copy": "Sao chép",
"Copy Zulip URL": "Sao chép đường dẫn",
"Create a new organization": "Tạo nhóm mới",
"Cut": "Cắt",
"Default download location": "Nơi lưu tập tin mặc định",
"Delete": "Xóa",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Thông báo trên giao diện máy tính",
"Desktop Settings": "Cài đặt ứng dụng",
"Disconnect": "Ngắt kết nối",
"Download App Logs": "Download App Logs",
"Edit": "Chỉnh sửa",
"Edit Shortcuts": "Edit Shortcuts",
"Enable auto updates": "Enable auto updates",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Factory Reset": "Factory Reset",
"File": "File",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
"Flash taskbar on new message": "Flash taskbar on new message",
"Forward": "Forward",
"Functionality": "Functionality",
"General": "General",
"Get beta updates": "Get beta updates",
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Log Out": "Log Out",
"Log Out of Organization": "Log Out of Organization",
"Manual proxy configuration": "Manual proxy configuration",
"Minimize": "Minimize",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"OR": "OR",
"Organization URL": "Organization URL",
"Organizations": "Organizations",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Proxy": "Proxy",
"Proxy bypass rules": "Proxy bypass rules",
"Proxy rules": "Proxy rules",
"Quit": "Thoát",
"Quit Zulip": "Thoát khỏi Zulip",
"Redo": "Thực hiện lại",
"Release Notes": "Release Notes",
"Reload": "Tải lại",
"Report an Issue": "Report an Issue",
"Save": "Lưu",
"Select All": "Select All",
"Settings": "Cài đặt",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "Mọi dữ liệu trong ứng dụng, bao gồm tất cả tài khoản và tùy chỉnh được thêm vào, sẽ bị xóa",
"Tip": "Mẹo",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Công cụ",
"Undo": "Hủy thay đổi",
"Upload": "Tải lên",
"Use system proxy settings (requires restart)": "Chọn cài đặt system proxy (Yêu cầu khởi động lại)",
"View": "Xem",
"View Shortcuts": "Hiển thị phím tắt",
"Window": "Ứng dụng Window",
"Window Shortcuts": "Phím tắt trong Windows",
"YES": "Có",
"Zoom In": "Phóng to",
"Zoom Out": "Thu nhỏ",
"Zulip Help": "Trợ giúp",
"keyboard shortcuts": "Phím tắt bàn phím",
"script": "script",
"Quit when the window is closed": "Thoát khi cửa sổ tắt",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
}

View File

@@ -1,134 +0,0 @@
{
"About Zulip": "关于Zulip",
"Actual Size": "真实大小",
"Add Custom Certificates": "添加自定义证书\n\n",
"Add Organization": "添加组织\n\n",
"Add a Zulip organization": "添加Zulip组织\n\n",
"Add custom CSS": "添加自定义样式(CSS)",
"Advanced": "高级",
"All the connected organizations will appear here": "All the connected organizations will appear here",
"Always start minimized": "Always start minimized",
"App Updates": "应用更新",
"Appearance": "Appearance",
"Application Shortcuts": "快捷键",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "后退",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"Change": "更改",
"Check for Updates": "检查更新...",
"Close": "关闭",
"Connect": "连接",
"Connect to another organization": "连接到另一个组织",
"Connected organizations": "连接的组织",
"Copy": "复制",
"Copy Zulip URL": "复制Zulip地址(URL)",
"Create a new organization": "创建新的组织",
"Cut": "剪切",
"Default download location": "缺省下载位置",
"Delete": "删除",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Desktop Settings",
"Disconnect": "断开",
"Download App Logs": "Download App Logs",
"Edit": "编辑",
"Edit Shortcuts": "Edit Shortcuts",
"Enable auto updates": "Enable auto updates",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Factory Reset": "Factory Reset",
"File": "文件",
"Find accounts": "查找账户",
"Find accounts by email": "Find accounts by email",
"Flash taskbar on new message": "Flash taskbar on new message",
"Forward": "Forward",
"Functionality": "Functionality",
"General": "General",
"Get beta updates": "Get beta updates",
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"History": "历史消息",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Log Out": "退出",
"Log Out of Organization": "Log Out of Organization",
"Manual proxy configuration": "Manual proxy configuration",
"Minimize": "最小化",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"OR": "或",
"Organization URL": "社群网址",
"Organizations": "Organizations",
"Paste": "粘贴",
"Paste and Match Style": "粘贴并匹配格式",
"Proxy": "Proxy",
"Proxy bypass rules": "Proxy bypass rules",
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"Report an Issue": "Report an Issue",
"Save": "保存",
"Select All": "全选",
"Settings": "设置",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "显示侧边栏",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "工具",
"Undo": "撤销",
"Upload": "上传",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
"View Shortcuts": "View Shortcuts",
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "确认",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
</dict>
</plist>

View File

@@ -2,6 +2,48 @@
All notable changes to the Zulip desktop app are documented in this file.
### v5.10.0 --2023-05-05
**Removed features**:
- Removed support for Windows 8.1 and earlier, which reached end-of-life earlier this year and are [no longer supported](https://www.electronjs.org/blog/windows-7-to-8-1-deprecation-notice) by Electron.
- Removed support for Zulip Server 3.x and earlier, which have been obsolete for more than 18 months, in accordance with our [release lifecycle](https://zulip.readthedocs.io/en/latest/overview/release-lifecycle.html). A notice will now be displayed when connecting to a server with an unsupported version.
**Fixes**:
- Fixed display of the dropdown for the spellchecker languages setting.
- Fixed various bugs related to displaying and updating organization icons.
- Fixed settings to disable visual display of notifications.
**Dependencies**:
- Upgraded all dependencies, including Electron 24.2.0.
### v5.9.5 --2023-02-06
**Fixes**:
- Fixed a hang on startup when an organization cannot be connected at startup.
**Enhancements**:
- Enabled Chromium sandboxing in remote renderer processes for improved security hardening.
**Dependencies**:
- Upgraded all dependencies, including Electron 22.2.0.
### v5.9.4 --2023-01-04
**Fixes**:
- The `com.apple.quarantine` extended attribute is now correctly set for downloaded files on macOS.
- The external link handler ignores invalid URLs.
**Dependencies**:
- Upgraded all dependencies, including Electron 22.0.0.
### v5.9.3 --2022-04-28
**Fixes**:
@@ -168,10 +210,10 @@ All notable changes to the Zulip desktop app are documented in this file.
**New features**:
- Add a cancel button in the report-issue modal.
- macOS: Use electron API to get dark tray icon instead of the green icon for the light theme.
- macOS: Use Electron API to get dark tray icon instead of the green icon for the light theme.
- Remove 'Reset App Data' option. Factory Reset option has been moved to Settings → General.
- Support pkg installer on macOS.
- Use electron 8 built-in spellchecker. Linux and Windows users can now choose upto three spellchecker languages from Settings → General. On macOS, default spellchecker is used.
- Use Electron 8 built-in spellchecker. Linux and Windows users can now choose up to three spellchecker languages from Settings → General. On macOS, default spellchecker is used.
- Setup Transifex for better synchronization of translations. The application now supports 41 languages instead of 21.
**Dependencies**:
@@ -279,7 +321,7 @@ All notable changes to the Zulip desktop app are documented in this file.
- Document enterprise configuration features.
- Update the Electron tutorial guide.
- Explicitly address where to report bugs in `README.md`.
- Fix typo in the link to server/webapp repository in `README.md`.
- Fix typo in the link to server/web app repository in `README.md`.
- Add documentation for translation.
### v4.0.0 --2019-08-08
@@ -330,7 +372,7 @@ All notable changes to the Zulip desktop app are documented in this file.
**Development**:
- Migrate codebase to TypeScript.
- Set the indent_size in `.editconfig` to 4.
- Set the indent_size in `.editorconfig` to 4.
- Use `.env` file for reading Sentry DSN.
**Documentation**:
@@ -399,7 +441,7 @@ All notable changes to the Zulip desktop app are documented in this file.
- Fix typo in network error message.
- Fix context menu not working on adding new org.
- Fix reply from notification.
- Fix shorcut section horizontal alignment.
- Fix shortcut section horizontal alignment.
- Fix broken link in docs.
- Fix grammatical errors.
- Fix typo error in issue template.
@@ -438,7 +480,7 @@ All notable changes to the Zulip desktop app are documented in this file.
- Auto hide menubar on Windows/Linux. Add a setting option for the same.
- Improve design of setting page.
- Toggle app on clicking the tray icon (Linux).
- Update sidebar realm name when it's changed in webapp.
- Update sidebar realm name when it's changed in web app.
- left-sidebar: Add initial character of realm name instead of default icon.
**Fixes**:
@@ -475,7 +517,7 @@ All notable changes to the Zulip desktop app are documented in this file.
**Fixes**:
- Fix youtube video not playing in lightbox.
- Fix YouTube video not playing in lightbox.
- Fix realm name not escaped properly.
<hr>
@@ -485,7 +527,7 @@ All notable changes to the Zulip desktop app are documented in this file.
**New features**:
- Add a setting option to show downloaded file in file manager.
- Added electron bridge to communicate with webapp in real time.
- Added Electron bridge to communicate with web app in real time.
**Fixes**:
@@ -598,7 +640,7 @@ electron-updater - `v2.21.8`
- Add an option to download the file attachments instead of opening it in the browser
- Open image link in webapp lightbox
- Open image link in web app lightbox
- Add scrollbar for list of organizations on overflow
@@ -635,7 +677,7 @@ electron-updater - `v2.21.8`
- Some users wanted to change the look of the Zulip. Now you have the power. Feel free to add your own CSS using the all-new setting option **Add Custom CSS**
- Added i18n locale helper script. Internalization is coming in the next release
- Added i18n locale helper script. Internationalization is coming in the next release
- Added **What's new** in `help` submenu so that you can see all the latest changes in the app
@@ -1077,7 +1119,7 @@ Minor improvements
- Using two package.json structure
- Node integration disabled in main window due to jquery error
- Node integration disabled in main window due to jQuery error
- Now using electron-builder for packaging instead of electron-packager

View File

@@ -49,7 +49,7 @@ If [NPM](https://www.npmjs.com/get-npm) and [node-gyp](https://github.com/nodejs
[node-windows]: https://nodejs.org/en/download/package-manager/#windows
- Also, install install Windows-Build-Tools to compile native node modules by using
- Also, install Windows-Build-Tools to compile native node modules by using
```sh
$ npm install --global windows-build-tools
```

View File

@@ -5,13 +5,13 @@
- [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [Node.js](https://nodejs.org) >= v6.9.0
- [python](https://www.python.org/downloads/release/python-2713/) (v2.7.x recommended)
- [node-gyp](https://github.com/nodejs/node-gyp#installation) (installed via powershell)
- [node-gyp](https://github.com/nodejs/node-gyp#installation) (installed via PowerShell)
## System specific dependencies
- use only 32bit or 64bit for all of the installers, do not mix architectures
- install using default settings
- open Windows Powershell as Admin
- open Windows PowerShell as Admin
```powershell
C:\Windows\system32> npm install --global --production windows-build-tools

View File

@@ -38,7 +38,7 @@ You'll want Transifex's CLI client, `tx`.
Run `tx push -s`.
This uploads from `app/translations/en.json` to the
This uploads from `public/translations/en.json` to the
set of strings Transifex shows for contributors to translate.
(See `.tx/config` for how that's configured.)
@@ -46,7 +46,7 @@ set of strings Transifex shows for contributors to translate.
Run `tools/tx-pull`.
This writes to files `app/translations/<lang>.json`.
This writes to files `public/translations/<lang>.json`.
(See `.tx/config` for how that's configured.)
Then look at the following sections to see if further updates are
@@ -59,7 +59,7 @@ language. This happens when we've opened up a new language for people
to contribute translations into in the Zulip project on Transifex,
which we do when someone expresses interest in contributing them.
The locales for supported languages are stored in `app/translations/supported-locales.json`
The locales for supported languages are stored in `public/translations/supported-locales.json`
So, when a new language is added, update the `supported-locales` module.

View File

@@ -7,7 +7,7 @@
[lr]: https://github.com/zulip/zulip-desktop/releases
## OS X
## macOS
**DMG or zip**:
@@ -17,7 +17,7 @@
**Using brew**:
1. Run `brew cask install zulip` in your terminal
1. Run `brew install --cask zulip` in your terminal
2. The app will be installed in your `Applications`
3. Done! The app will update automatically (you can also use `brew update && brew upgrade zulip`)
@@ -53,20 +53,20 @@
- First download our signing key to make sure the deb you download is correct:
```
sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9
```bash
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9
```
- Add the repo to your apt source list :
```
echo "deb https://dl.bintray.com/zulip/debian/ beta main" |
```bash
echo "deb https://download.zulip.com/desktop/apt stable main" |
sudo tee -a /etc/apt/sources.list.d/zulip.list
```
- Now install the client :
```
```bash
sudo apt-get update
sudo apt-get install zulip
```

14131
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "5.9.3",
"main": "./app/main",
"version": "5.10.0",
"main": "./dist-electron",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -18,11 +18,10 @@
"url": "https://github.com/zulip/zulip-desktop/issues"
},
"engines": {
"node": ">=12.10.0"
"node": ">=16.13.2"
},
"scripts": {
"start": "tsc && electron .",
"clean-ts-files": "git clean \"app/*.js\" -xf",
"start": "vite",
"watch-ts": "tsc -w",
"reinstall": "rimraf node_modules && npm install",
"postinstall": "electron-builder install-app-deps",
@@ -30,11 +29,11 @@
"lint-html": "htmlhint \"app/**/*.html\"",
"lint-js": "xo",
"prettier-non-js": "prettier --check --loglevel=warn . \"!**/*.{js,ts}\"",
"test": "tsc --noEmit && npm run lint-html && npm run lint-css && npm run lint-js && npm run prettier-non-js",
"test-e2e": "tsc && tape \"tests/**/*.js\"",
"pack": "tsc && electron-builder --dir",
"dist": "tsc && electron-builder",
"mas": "tsc && electron-builder --mac mas"
"test": "tsc && npm run lint-html && npm run lint-css && npm run lint-js && npm run prettier-non-js",
"test-e2e": "vite build && tape \"tests/**/*.js\"",
"pack": "vite build && electron-builder --dir",
"dist": "vite build && electron-builder",
"mas": "vite build && electron-builder --mac mas"
},
"pre-commit": [
"test"
@@ -47,7 +46,7 @@
"**/*.node"
],
"files": [
"app/**/*"
"dist-electron/**/*"
],
"copyright": "©2020 Kandra Labs, Inc.",
"mac": {
@@ -69,11 +68,7 @@
}
],
"darkModeSupport": true,
"artifactName": "${productName}-${version}-${arch}.${ext}",
"hardenedRuntime": true,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"gatekeeperAssess": false
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
@@ -146,48 +141,49 @@
"InstantMessaging"
],
"dependencies": {
"@electron/remote": "^2.0.8",
"@sentry/electron": "^3.0.3",
"@yaireo/tagify": "^4.5.0",
"adm-zip": "^0.5.5",
"auto-launch": "^5.0.5",
"backoff": "^2.5.0",
"electron-log": "^4.3.5",
"electron-updater": "^5.0.1",
"electron-window-state": "^5.0.3",
"escape-goat": "^3.0.0",
"get-stream": "^6.0.1",
"i18n": "^0.14.1",
"iso-639-1": "^2.1.9",
"node-json-db": "^1.3.0",
"semver": "^7.3.5",
"zod": "^3.5.1"
"gatemaker": "^1.0.0"
},
"devDependencies": {
"@electron/notarize": "^1.2.3",
"@electron/remote": "^2.0.8",
"@sentry/electron": "^4.1.2",
"@types/adm-zip": "^0.5.0",
"@types/auto-launch": "^5.0.2",
"@types/backoff": "^2.5.2",
"@types/i18n": "^0.13.1",
"@types/node": "^16.11.26",
"@types/node": "^18.16.5",
"@types/requestidlecallback": "^0.3.4",
"@types/yaireo__tagify": "^4.3.2",
"@yaireo/tagify": "^4.5.0",
"adm-zip": "^0.5.5",
"auto-launch": "^5.0.5",
"backoff": "^2.5.0",
"dotenv": "^16.0.0",
"electron": "^18.0.1",
"electron": "^24.2.0",
"electron-builder": "^23.0.3",
"electron-notarize": "^1.0.0",
"eslint-import-resolver-typescript": "^2.4.0",
"electron-log": "^4.3.5",
"electron-updater": "^5.0.1",
"electron-window-state": "^5.0.3",
"escape-goat": "^4.0.0",
"get-stream": "^6.0.1",
"htmlhint": "^1.1.2",
"i18n": "^0.15.1",
"iso-639-1": "^2.1.9",
"medium": "^1.2.0",
"playwright-core": "^1.19.1",
"node-json-db": "^1.3.0",
"playwright-core": "^1.30.0-alpha-jan-3-2023",
"pre-commit": "^1.2.2",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"stylelint": "^14.5.3",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^25.0.0",
"rimraf": "^5.0.0",
"semver": "^7.3.5",
"stylelint": "^15.6.1",
"stylelint-config-standard": "^33.0.0",
"tape": "^5.2.2",
"typescript": "^4.3.5",
"xo": "^0.48.0"
"typescript": "^5.0.4",
"vite": "^4.1.1",
"vite-plugin-electron": "^0.11.1",
"xo": "^0.54.2",
"zod": "^3.5.1"
},
"prettier": {
"bracketSpacing": false,
@@ -199,15 +195,6 @@
"rules": {
"@typescript-eslint/no-dynamic-delete": "off",
"arrow-body-style": "error",
"import/extensions": [
"error",
"always",
{
"pattern": {
"ts": "never"
}
}
],
"import/no-restricted-paths": [
"error",
{
@@ -216,8 +203,7 @@
"target": "./app/common",
"from": "./app",
"except": [
"./common",
"./translations"
"./common"
]
},
{
@@ -225,8 +211,7 @@
"from": "./app",
"except": [
"./common",
"./main",
"./translations"
"./main"
]
},
{
@@ -235,7 +220,7 @@
"except": [
"./common",
"./renderer",
"./translations"
"./resources"
]
}
]
@@ -284,8 +269,8 @@
}
],
"strict": "error",
"unicorn/prefer-json-parse-buffer": "off",
"unicorn/prefer-module": "off"
"unicorn/prefer-module": "off",
"unicorn/prefer-top-level-await": "off"
},
"envs": [
"node",
@@ -312,14 +297,10 @@
}
],
"unicorn/no-await-expression-member": "off"
},
"settings": {
"import/resolver": "typescript"
}
},
{
"files": [
"app/renderer/js/injected.ts",
"scripts/notarize.js",
"tests/**/*.js"
],

View File

@@ -1,10 +1,10 @@
#!/bin/bash
# Link to the binary
ln -sf '/opt/${productFilename}/${executable}' '/usr/bin/${executable}'
ln -sf '/opt/${sanitizedProductName}/${executable}' '/usr/bin/${executable}'
# SUID chrome-sandbox for Electron 5+
chmod 4755 '/opt/${productFilename}/chrome-sandbox' || true
chmod 4755 '/opt/${sanitizedProductName}/chrome-sandbox' || true
update-mime-database /usr/share/mime || true
update-desktop-database /usr/share/applications || true

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 321 B

View File

Before

Width:  |  Height:  |  Size: 631 B

After

Width:  |  Height:  |  Size: 631 B

View File

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 932 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -1,45 +1,49 @@
{
"About Zulip": "حول \"زوليب\"",
"Actual Size": "الحجم الفعلي",
"Add Custom Certificates": "إضافة رخصة معدلة",
"Add Organization": "إضافة منظمة",
"Add a Zulip organization": "إضافة منظمة \"زوليب\"",
"Add custom CSS": صافة CSS معدلة",
"Add custom CSS": ضافة CSS معدلة",
"AddServer": "AddServer",
"Advanced": "متقدم",
"All the connected organizations will appear here": "جميع المنظمات المتصلة تعرض هنا",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "دائماً إبدأ بالقليل",
"App Updates": "تحديث التطبيق",
"App Updates": "تحديثات التطبيق",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "المظهر",
"Application Shortcuts": "إختصارات التطبيق",
"Are you sure you want to disconnect this organization?": "هل أنت متأكد من فصل هذة المنظمة؟",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "أخف القائمة تلقائياً",
"Auto hide menu bar (Press Alt key to display)": "أخف القائمة تلقائياً (إضغط Alt لعرض القائمة)",
"Back": "رجوع",
"Bounce dock on new private message": "أخرج المنصة في حال رسالة خاصة جديدة",
"Certificate file": "ملف الشهادة",
"Change": "تغيير",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Check for Updates": "التحقق من التحديثات",
"Close": "اغلاق",
"Close": "إغلاق",
"Connect": "اتصال",
"Connect to another organization": "التوصيل مع منظمة أخرى",
"Connected organizations": "المنظمات المتصلة",
"Copy": "نسخ",
"Copy Zulip URL": "نسخ رابط زوليب",
"Create a new organization": "Create a new organization",
"Create a new organization": "إنشاء منظمة جديدة",
"Cut": "قص",
"Default download location": "موقع التحميل الافتراضي",
"Delete": "حذف",
"Desktop App Settings": "إعدادت تطبيق سطح المكتب",
"Desktop Notifications": "إشعارات سطح المكتب",
"Desktop Settings": "إعدادات سطح المكتب",
"Disconnect": "قطع الاتصال",
"Download App Logs": "تنزيل سجلات التطبيق",
"Edit": "تعديل",
"Edit Shortcuts": "تعديل الاختصارات",
"Emoji & Symbols": "Emoji & Symbols",
"Enable auto updates": "تفعيل التحديثات التلقائية",
"Enable error reporting (requires restart)": "تفعيل تقارير الأخطاء (يتطلب إعادة التشغيل)",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Enter Full Screen": "Enter Full Screen",
"Factory Reset": "إعادة ضبط المصنع",
"Factory Reset Data": "Factory Reset Data",
"File": "ملف",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
@@ -51,6 +55,9 @@
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "OR",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "Organization URL",
"Organizations": "Organizations",
"Paste": "Paste",
@@ -71,25 +80,27 @@
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"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.",
"Save": "Save",
"Select All": "Select All",
"Services": "Services",
"Settings": "Settings",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Unhide": "Unhide",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
@@ -106,29 +118,10 @@
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "Относно Zulip",
"Actual Size": "Действителен размер",
"Add Custom Certificates": "Добавяне на персонализирани сертификати",
"Add Organization": "Добавяне на организация",
"Add a Zulip organization": "Добавете организация Zulip",
"Add custom CSS": "Добавете персонализиран CSS",
"AddServer": "AddServer",
"Advanced": "напреднал",
"All the connected organizations will appear here": "Всички свързани организации ще се появят тук",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Винаги започвайте да минимизирате",
"App Updates": "Актуализации на приложения",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "Външен вид",
"Application Shortcuts": "Клавишни комбинации за приложения",
"Are you sure you want to disconnect this organization?": "Наистина ли искате да прекъснете връзката с тази организация?",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "Автоматично скриване на лентата с менюта",
"Auto hide menu bar (Press Alt key to display)": "Автоматично скриване на лентата с менюта (натиснете клавиша Alt за показване)",
"Back": "обратно",
"Bounce dock on new private message": "Прескочи док в новото лично съобщение",
"Certificate file": "Файл за сертификат",
"Change": "промяна",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Check for Updates": "Провери за обновления",
"Close": "Близо",
"Connect": "Свържете",
@@ -29,17 +31,19 @@
"Cut": "Разрез",
"Default download location": "Място на изтегляне по подразбиране",
"Delete": "Изтрий",
"Desktop App Settings": "Настройки на приложението за работния плот",
"Desktop Notifications": "Известия за работния плот",
"Desktop Settings": "Настройки на работния плот",
"Disconnect": "Прекъсване на връзката",
"Download App Logs": "Изтеглете регистрационни файлове на приложенията",
"Edit": "редактиране",
"Edit Shortcuts": "Редактиране на преки пътища",
"Emoji & Symbols": "Emoji & Symbols",
"Enable auto updates": "Активиране на автоматичните актуализации",
"Enable error reporting (requires restart)": "Активиране на отчитането за грешки (изисква се рестартиране)",
"Enable spellchecker (requires restart)": "Активиране на проверката на правописа (изисква се рестартиране)",
"Enter Full Screen": "Enter Full Screen",
"Factory Reset": "Фабрично нулиране",
"Factory Reset Data": "Factory Reset Data",
"File": "досие",
"Find accounts": "Намерете профили",
"Find accounts by email": "Намерете профили по имейл",
@@ -51,6 +55,9 @@
"Hard Reload": "Hard Reload",
"Help": "Помогне",
"Help Center": "Помощен център",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "история",
"History Shortcuts": "Преки пътища в историята",
"Keyboard Shortcuts": "Комбинация от клавиши",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Заглуши всички звуци от Zulip",
"NO": "НЕ",
"Network": "мрежа",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "ИЛИ",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "URL адрес на организацията",
"Organizations": "организации",
"Paste": "паста",
@@ -71,25 +80,27 @@
"Proxy rules": "Прокси правила",
"Quit": "напускам",
"Quit Zulip": "Прекрати Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "ремонтирам",
"Release Notes": "Бележки към изданието",
"Reload": "Презареди",
"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.",
"Save": "Запази",
"Select All": "Избери всички",
"Services": "Services",
"Settings": "Настройки",
"Shortcuts": "Shortcuts",
"Show App Logs": "Показване на регистрационните файлове на приложенията",
"Show app icon in system tray": "Показване на иконата на приложението в системната област",
"Show app unread badge": "Показване на непрочетената значка на приложението",
"Show desktop notifications": "Показване на известията на работния плот",
"Show downloaded files in file manager": "Показване на изтеглените файлове във файловия мениджър",
"Show sidebar": "Показване на страничната лента",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Стартирайте приложението при влизане",
"Switch to Next Organization": "Превключване към следваща организация",
"Switch to Previous Organization": "Превключване към предишна организация",
"These desktop app shortcuts extend the Zulip webapp's": "Тези клавишни комбинации за настолни приложения разширяват webapp на Zulip",
"This will delete all application data including all added accounts and preferences": "Това ще изтрие всички данни за приложения, включително всички добавени акаунти и предпочитания",
"Tip": "Бакшиш",
"Toggle DevTools for Active Tab": "Превключете DevTools за Active Tab",
"Toggle DevTools for Zulip App": "Превключете DevTools за Zulip App",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Превключете иконата на тава",
"Tools": "Инструменти",
"Undo": "премахвам",
"Unhide": "Unhide",
"Upload": "Качи",
"Use system proxy settings (requires restart)": "Използване на системните прокси настройки (изисква рестартиране)",
"View": "изглед",
@@ -106,29 +118,10 @@
"Window": "прозорец",
"Window Shortcuts": "Клавишни комбинации",
"YES": "ДА",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Увеличавам",
"Zoom Out": "Отдалечавам",
"Zulip Help": "Помощ за Zulip",
"keyboard shortcuts": "комбинация от клавиши",
"script": "писменост",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "Quant a Zulip",
"Actual Size": "Actual Size",
"Add Custom Certificates": "Add Custom Certificates",
"Add Organization": "Add Organization",
"Add a Zulip organization": "Add a Zulip organization",
"Add custom CSS": "Add custom CSS",
"AddServer": "AddServer",
"Advanced": "Advanced",
"All the connected organizations will appear here": "All the connected organizations will appear here",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Always start minimized",
"App Updates": "App Updates",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "Appearance",
"Application Shortcuts": "Application Shortcuts",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "Back",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"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",
"Close": "Tancar",
"Connect": "Connect",
@@ -29,17 +31,19 @@
"Cut": "Cut",
"Default download location": "Default download location",
"Delete": "Elimina",
"Desktop App Settings": "Configuració de l'aplicació d'escriptori",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Configuració d'escriptori",
"Disconnect": "Disconnect",
"Download App Logs": "Download App Logs",
"Edit": "Edita",
"Edit Shortcuts": "Edit Shortcuts",
"Emoji & Symbols": "Emoji & Symbols",
"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",
"Factory Reset": "Factory Reset",
"Factory Reset Data": "Factory Reset Data",
"File": "Fitxer",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
@@ -48,20 +52,25 @@
"Functionality": "Functionality",
"General": "General",
"Get beta updates": "Get beta updates",
"Hard Reload": "Hard Reload",
"Hard Reload": "Recàrrega forçada",
"Help": "Help",
"Help Center": "Centre d'ajuda",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "Historial",
"History Shortcuts": "Dreceres d'historial",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Log Out": "Log Out",
"Log Out of Organization": "Log Out of Organization",
"Log Out": "Tanca la sessió",
"Log Out of Organization": "Tanca la sessió de l'organització",
"Manual proxy configuration": "Manual proxy configuration",
"Minimize": "Minimize",
"Mute all sounds from Zulip": "Silencia tots els sons de Zulip",
"NO": "NO",
"Network": "Network",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "OR",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "URL d'organització",
"Organizations": "Organizations",
"Paste": "Paste",
@@ -71,25 +80,27 @@
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"Reload": "Recarrega",
"Report an Issue": "Report an Issue",
"Reset App Settings": "Reinicia la configuració de l'aplicació",
"Reset the application, thus deleting all the connected organizations and accounts.": "Reset the application, thus deleting all the connected organizations and accounts.",
"Save": "Guardar",
"Select All": "Select All",
"Services": "Services",
"Settings": "Configuració",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Mostrar una marca en la icona si hi ha missatges no llegits",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Unhide": "Unhide",
"Upload": "Pujada",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
@@ -106,29 +118,10 @@
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "O Zulipu",
"Actual Size": "Skutečná velikost",
"Add Custom Certificates": "Přidat vlastní certifikáty",
"Add Organization": "Přidat organizaci",
"Add a Zulip organization": "Přidat organizaci Zulip",
"Add custom CSS": "Přidat vlastní CSS",
"AddServer": "Přidat server",
"Advanced": "Rozšířené",
"All the connected organizations will appear here": "Všechny připojené organizace se objeví zde",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Vždy spouštět minimalizované",
"App Updates": "Aktualizace aplikace",
"App language (requires restart)": "Jazyk programu (vyžaduje opětovné spuštění programu)",
"Appearance": "Vzhled",
"Application Shortcuts": "Zkratky programu",
"Are you sure you want to disconnect this organization?": "Opravdu chcete odpojit tuto organizaci?",
"Ask where to save files before downloading": "Před stažením se zeptat kam uložit soubory",
"Auto hide Menu bar": "Automaticky skrývat menu",
"Auto hide menu bar (Press Alt key to display)": "Automaticky skrývat menu (pro zobrazení stiskněte klávesu Alt)",
"Back": "Zpět",
"Bounce dock on new private message": "Poskakování ikony v docku po obdržení nové soukromé zprávy",
"Certificate file": "Soubor s certifikátem",
"Change": "Změnit",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Změnit jazyk v Nastavení systému → Klávesnice → Text → Kontrola pravopisu.",
"Check for Updates": "Zkontrolovat aktualizace",
"Close": "Zavřít",
"Connect": "Připojit se",
@@ -29,17 +31,19 @@
"Cut": "Vyjmout",
"Default download location": "Výchozí umístění stahování",
"Delete": "Smazat",
"Desktop App Settings": "Nastavení desktopové aplikace",
"Desktop Notifications": "Oznámení na ploše",
"Desktop Settings": "Nastavení plochy",
"Disconnect": "Odpojit",
"Download App Logs": "Stáhnout záznamy programu",
"Edit": "Upravit",
"Edit Shortcuts": "Upravit zkratky",
"Emoji & Symbols": "Emoji & Symbols",
"Enable auto updates": "Povolit automatické aktualizace",
"Enable error reporting (requires restart)": "Povolit hlášení chyb (vyžaduje opětovné spuštění programu)",
"Enable spellchecker (requires restart)": "Povolit kontrolu pravopisu (vyžaduje opětovné spuštění programu)",
"Enter Full Screen": "Enter Full Screen",
"Factory Reset": "Obnovení do továrního nastavení",
"Factory Reset Data": "Obnovení dat do továrního nastavení",
"File": "Soubor",
"Find accounts": "Najít účty",
"Find accounts by email": "Najít účty podle adresy elektronické pošty",
@@ -51,6 +55,9 @@
"Hard Reload": "Tvrdé znovunahrání",
"Help": "Nápověda",
"Help Center": "Centrum nápovědy",
"Hide": "Skrýt",
"Hide Others": "Skrýt jiné",
"Hide Zulip": "Hide Zulip",
"History": "Historie",
"History Shortcuts": "Zkratky pro historii",
"Keyboard Shortcuts": "Klávesové zkratky",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Ztlumit všechny zvuky ze Zulipu",
"NO": "NE",
"Network": "Síť",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "NEBO",
"On macOS, the OS spellchecker is used.": "Na macOS se používá kontrola pravopisu OS.",
"Organization URL": "Adresa organizace",
"Organizations": "Organizace",
"Paste": "Vložit",
@@ -71,25 +80,27 @@
"Proxy rules": "Pravidla Proxy",
"Quit": "Ukončit",
"Quit Zulip": "Ukončit Zulip",
"Quit when the window is closed": "Ukončit, když je okno zavřeno",
"Redo": "Znovu",
"Release Notes": "Poznámky k této verzi",
"Reload": "Nahrát znovu",
"Report an Issue": "Nahlásit problém",
"Reset App Settings": "Obnovit výchozí nastavení programu",
"Reset the application, thus deleting all the connected organizations and accounts.": "Reset the application, thus deleting all the connected organizations and accounts.",
"Save": "Uložit",
"Select All": "Vybrat vše",
"Services": "Služby",
"Settings": "Nastavení",
"Shortcuts": "Zkratky",
"Show App Logs": "Zobrazit záznamy programu",
"Show app icon in system tray": "Zobrazovat ikonu programu v oznamovací oblasti panelu",
"Show app unread badge": "Zobrazovat u ikony aplikace symbol nepřečteno",
"Show desktop notifications": "Zobrazovat oznámení na ploše",
"Show downloaded files in file manager": "Zobrazit stažené soubory ve správci souborů",
"Show sidebar": "Zobrazovat postranní panel",
"Spellchecker Languages": "Kontrola pravopisu jazyků",
"Start app at login": "Spustit program při přihlášení",
"Switch to Next Organization": "Přepnout na další organizaci",
"Switch to Previous Organization": "Přepnout na předchozí organizaci",
"These desktop app shortcuts extend the Zulip webapp's": "Tyto zkratky rozšiřují webovou aplikaci Zulipu",
"This will delete all application data including all added accounts and preferences": "Toto smaže všechna data programu včetně všech přidaných účtů a nastavení",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Přepnout vývojářské nástroje pro aktivní kartu",
"Toggle DevTools for Zulip App": "Přepnout vývojářské nástroje pro program Zulip",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Přepnout ikonu v oznamovací oblasti panelu",
"Tools": "Nástroje",
"Undo": "Zpět",
"Unhide": "Zobrazit",
"Upload": "Nahrát",
"Use system proxy settings (requires restart)": "Použít systémová nastavení proxy (vyžaduje opětovné spuštění programu)",
"View": "Zobrazení",
@@ -106,29 +118,10 @@
"Window": "Okno",
"Window Shortcuts": "Zkratky pro okno",
"YES": "ANO",
"You can select a maximum of 3 languages for spellchecking.": "Pro kontrolu pravopisu můžete vybrat nejvíce 3 jazyky.",
"Zoom In": "Přiblížit",
"Zoom Out": "Oddálit",
"Zulip Help": "Nápověda Zulipu",
"keyboard shortcuts": "klávesové zkratky",
"script": "skript",
"Quit when the window is closed": "Ukončit, když je okno zavřeno",
"Ask where to save files before downloading": "Před stažením se zeptat kam uložit soubory",
"Services": "Služby",
"Hide": "Skrýt",
"Hide Others": "Skrýt jiné",
"Unhide": "Zobrazit",
"AddServer": "Přidat server",
"App language (requires restart)": "Jazyk programu (vyžaduje opětovné spuštění programu)",
"Factory Reset Data": "Obnovení dat do továrního nastavení",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Obnovit program do výchozího nastavení. čili smazat všechny připojené organizace, účty a certifikáty.",
"On macOS, the OS spellchecker is used.": "Na macOS se používá kontrola pravopisu OS.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Změnit jazyk v Nastavení systému → Klávesnice → Text → Kontrola pravopisu.",
"Copy Link": "Kopírovat odkaz",
"Copy Image": "Kopírovat obrázek",
"Copy Image URL": "Kopírovat adresu (URL) obrázku",
"No Suggestion Found": "Nenalezen žádný návrh",
"You can select a maximum of 3 languages for spellchecking.": "Pro kontrolu pravopisu můžete vybrat nejvíce 3 jazyky.",
"Spellchecker Languages": "Kontrola pravopisu jazyků",
"Add to Dictionary": "Přidat do slovníku",
"Look Up": "Vyhledat"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "Om Zulip",
"Actual Size": "Faktisk størrelse",
"Add Custom Certificates": "Tilføj egne certifikater",
"Add Organization": "Opret organisation",
"Add a Zulip organization": "Opret en Zulip organisation",
"Add custom CSS": "Tilføj egen CSS",
"AddServer": "AddServer",
"Advanced": "Avanceret",
"All the connected organizations will appear here": "Alle de tilsluttede organisationer vil blive vist her",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Start altid minimeret",
"App Updates": "App-opdateringer",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "Udseende",
"Application Shortcuts": "Genveje",
"Are you sure you want to disconnect this organization?": "Er du sikker på du vil frakoble denne organisation? ",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "Skjul menu automatisk",
"Auto hide menu bar (Press Alt key to display)": "Skjul menu automatisk (tryk på Alt-tasten for at vise)",
"Back": "Tilbage",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certifikat-fil",
"Bounce dock on new private message": "Animér dock ved ny privat meddelelse",
"Change": "Skift",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Check for Updates": "Tjek for opdateringer",
"Close": "Luk",
"Connect": "Tilslut",
@@ -29,28 +31,33 @@
"Cut": "Klip",
"Default download location": "Default download placering",
"Delete": "Slet",
"Desktop App Settings": "Desktop app-indstillinger",
"Desktop Notifications": "Desktop-notifikationer",
"Desktop Settings": "Desktop-indstillinger",
"Disconnect": "Frakobl",
"Download App Logs": "Download app-logfiler",
"Edit": "Redigér",
"Edit Shortcuts": "Redigér genveje",
"Emoji & Symbols": "Emoji & Symbols",
"Enable auto updates": "Aktivér auto-opdateringer",
"Enable error reporting (requires restart)": "Aktivér fejlrapportering (kræver genstart)",
"Enable spellchecker (requires restart)": "Aktivér stavekontrol (kræver genstart)",
"Enter Full Screen": "Enter Full Screen",
"Factory Reset": "Nulstil til fabriksindstillinger",
"Factory Reset Data": "Factory Reset Data",
"File": "Fil",
"Find accounts": "Find konti",
"Find accounts by email": "Find accounts by email",
"Flash taskbar on new message": "Flash taskbar on new message",
"Forward": "Forward",
"Find accounts by email": "Find konti via email-adresse",
"Flash taskbar on new message": "Animér proceslinjen ved ny besked",
"Forward": "Vidersend",
"Functionality": "Funktionalitet",
"General": "Generelt",
"Get beta updates": "Få beta opdateringer",
"Hard Reload": "Hård reload",
"Help": "Hjælp",
"Help Center": "Hjælpecenter",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "Historik",
"History Shortcuts": "Historik genveje",
"Keyboard Shortcuts": "Tastatur genveje",
@@ -58,10 +65,12 @@
"Log Out of Organization": "Log ud af organisation",
"Manual proxy configuration": "Manuel proxy opsætning",
"Minimize": "Minimér",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"Mute all sounds from Zulip": "Dæmp alle lyde fra Zulip",
"NO": "NEJ",
"Network": "Netværk",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "ELLER",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "Organisation URL",
"Organizations": "Organisationer",
"Paste": "Indsæt",
@@ -71,25 +80,27 @@
"Proxy rules": "Proxy regler",
"Quit": "Luk",
"Quit Zulip": "Luk Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"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.",
"Save": "Save",
"Select All": "Select All",
"Services": "Services",
"Settings": "Settings",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Unhide": "Unhide",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
@@ -106,29 +118,10 @@
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "Über Zulip",
"Actual Size": "Tatsächliche Größe",
"Add Custom Certificates": "Eigene Zertifikate hinzufügen",
"Add Organization": "Organisation hinzufügen",
"Add a Zulip organization": "Zulip-Organisation hinzufügen",
"Add custom CSS": "Eigenes CSS hinzufügen",
"AddServer": "ServerHinzufügen",
"Advanced": "Erweitert",
"All the connected organizations will appear here": "Alle verbundenen Organisationen erscheinen hier",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Immer minimiert öffnen",
"App Updates": "App Updates",
"App language (requires restart)": "Sprache der App (benötigt Neustart)",
"Appearance": "Erscheinungsbild",
"Application Shortcuts": "App-Verknüpfungen",
"Are you sure you want to disconnect this organization?": "Bist du dir sicher, dass du die Verbindung zur Organisation trennen möchtest?",
"Ask where to save files before downloading": "Fragen, wo heruntergeladene Dateien gespeichert werden sollen",
"Auto hide Menu bar": "Menü automatisch verstecken",
"Auto hide menu bar (Press Alt key to display)": "Menü automatisch verstecken (zum Anzeigen die Alt-Taste drücken)",
"Back": "Zurück",
"Bounce dock on new private message": "Im Dock hüpfen, wenn neue private Nachrichten eingehen",
"Certificate file": "Zertifikatsdatei",
"Change": "Ändern",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Ändere die Spracheinstellung über Systemeinstellungen → Tastatur → Text → Rechtschreibung.",
"Check for Updates": "Auf Updates prüfen",
"Close": "Schließen",
"Connect": "Verbinden",
@@ -29,17 +31,19 @@
"Cut": "Ausschneiden",
"Default download location": "Voreingestelltes Ziel für Downloads",
"Delete": "Löschen",
"Desktop App Settings": "Einstellungen für Desktop-App",
"Desktop Notifications": "Desktopbenachrichtigungen",
"Desktop Settings": "Desktop-Einstellungen",
"Disconnect": "Verbindung trennen",
"Download App Logs": "Logdateien der App herunterladen",
"Edit": "Bearbeiten",
"Edit Shortcuts": "Tastenkürzel bearbeiten",
"Emoji & Symbols": "Emoji & Symbols",
"Enable auto updates": "Automatisch aktualisieren",
"Enable error reporting (requires restart)": "Fehlerberichte aktivieren (erfordert Neustart)",
"Enable spellchecker (requires restart)": "Rechtschreibprüfung aktivieren (erfordert Neustart)",
"Enter Full Screen": "Enter Full Screen",
"Factory Reset": "Alle Einstellungen auf Standardwerte zurücksetzen",
"Factory Reset Data": "Auf Werkseinstellung zurücksetzen",
"File": "Datei",
"Find accounts": "Accounts finden",
"Find accounts by email": "Accounts anhand E-Mail-Adresse finden",
@@ -51,6 +55,9 @@
"Hard Reload": "Komplett neu laden",
"Help": "Hilfe",
"Help Center": "Hilfe-Zentrum",
"Hide": "Verbergen",
"Hide Others": "Andere verbergen",
"Hide Zulip": "Hide Zulip",
"History": "Verlauf",
"History Shortcuts": "Kurzbefehle für Verlauf",
"Keyboard Shortcuts": "Tastenkürzel",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Alle Zulip-Klänge stummschalten",
"NO": "NEIN",
"Network": "Netzwerk",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "ODER",
"On macOS, the OS spellchecker is used.": "In macOS wird die OS Rechtschreibprüfung verwendet.",
"Organization URL": "URL der Organisation",
"Organizations": "Organisationen",
"Paste": "Einfügen",
@@ -71,25 +80,27 @@
"Proxy rules": "Proxy-Regeln",
"Quit": "Beenden",
"Quit Zulip": "Zulip beenden",
"Quit when the window is closed": "Beim Schließen des Fensters beenden",
"Redo": "Wiederholen",
"Release Notes": "Hinweise zur Versionsfreigabe",
"Reload": "Neu laden",
"Report an Issue": "Ein Problem melden",
"Reset App Settings": "Einstellungen der App zurücksetzen",
"Reset the application, thus deleting all the connected organizations and accounts.": "Reset the application, thus deleting all the connected organizations and accounts.",
"Save": "Speichern",
"Select All": "Alles auswählen",
"Services": "Dienste",
"Settings": "Einstellungen",
"Shortcuts": "Kurzbefehle",
"Show App Logs": "Logdateien der App anzeigen",
"Show app icon in system tray": "App-Icon in Systemleiste anzeigen",
"Show app unread badge": "Anzahl ungelesener Nachrichten in App-Icon einblenden",
"Show desktop notifications": "Desktopbenachrichtigungen anzeigen",
"Show downloaded files in file manager": "Heruntergeladene Dateien in Dateimanager anzeigen",
"Show sidebar": "Seitenleiste anzeigen",
"Spellchecker Languages": "Sprachen für die Rechtschreibprüfung",
"Start app at login": "App beim Login automatisch starten",
"Switch to Next Organization": "Zur nächsten Organisation wechseln",
"Switch to Previous Organization": "Zur vorhergehenden Organisation wechseln",
"These desktop app shortcuts extend the Zulip webapp's": "Dies sind zusätzliche Kurzbefehle in der Desktop-App gegenüber der Web-App",
"This will delete all application data including all added accounts and preferences": "Hiermit werden alle Anwendungsdaten einschließlich aller Accounts und Einstellungen gelöscht",
"Tip": "Tipp",
"Toggle DevTools for Active Tab": "Entwickler-Tools für aktiven Tab umschalten",
"Toggle DevTools for Zulip App": "Entwickler-Tools für Zulip-App umschalten",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Tray-Icon umschalten",
"Tools": "Extras",
"Undo": "Rückgängig",
"Unhide": "Nicht mehr verbergen",
"Upload": "Hochladen",
"Use system proxy settings (requires restart)": "Systemweite Proxy-Einstellungen verwenden (erfordert Neustart)",
"View": "Ansicht",
@@ -106,29 +118,10 @@
"Window": "Fenster",
"Window Shortcuts": "Kurzbefehle für Fenster",
"YES": "JA",
"You can select a maximum of 3 languages for spellchecking.": "Du kannst höchstens 3 Sprachen für die Rechtschreibprüfung auswählen.",
"Zoom In": "Vergrößern",
"Zoom Out": "Verkleinern",
"Zulip Help": "Hilfe zu Zulip",
"keyboard shortcuts": "Tastenkürzel",
"script": "Skript",
"Quit when the window is closed": "Beim Schließen des Fensters beenden",
"Ask where to save files before downloading": "Fragen, wo heruntergeladene Dateien gespeichert werden sollen",
"Services": "Dienste",
"Hide": "Verbergen",
"Hide Others": "Andere verbergen",
"Unhide": "Nicht mehr verbergen",
"AddServer": "ServerHinzufügen",
"App language (requires restart)": "Sprache der App (benötigt Neustart)",
"Factory Reset Data": "Auf Werkseinstellung zurücksetzen",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Die App wird zurückgesetzt, somit werden alle verbundenen Organisationen, Konten und Zertifikate gelöscht.",
"On macOS, the OS spellchecker is used.": "In macOS wird die OS Rechtschreibprüfung verwendet.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Ändere die Spracheinstellung über Systemeinstellungen → Tastatur → Text → Rechtschreibung.",
"Copy Link": "Link kopieren",
"Copy Image": "Bild kopieren",
"Copy Image URL": "Bild-URL kopieren",
"No Suggestion Found": "Keine Vorschläge gefunden",
"You can select a maximum of 3 languages for spellchecking.": "Du kannst höchstens 3 Sprachen für die Rechtschreibprüfung auswählen.",
"Spellchecker Languages": "Sprachen für die Rechtschreibprüfung",
"Add to Dictionary": "Zum Wörterbuch hinzufügen",
"Look Up": "Nachschlagen"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "About Zulip",
"Actual Size": "Actual Size",
"Add Custom Certificates": "Add Custom Certificates",
"Add Organization": "Add Organization",
"Add a Zulip organization": "Add a Zulip organization",
"Add custom CSS": "Add custom CSS",
"AddServer": "AddServer",
"Advanced": "Advanced",
"All the connected organizations will appear here": "All the connected organizations will appear here",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Always start minimized",
"App Updates": "App Updates",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "Appearance",
"Application Shortcuts": "Application Shortcuts",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "Back",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"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",
"Close": "Close",
"Connect": "Connect",
@@ -29,17 +31,19 @@
"Cut": "Cut",
"Default download location": "Default download location",
"Delete": "Delete",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Desktop Settings",
"Disconnect": "Disconnect",
"Download App Logs": "Download App Logs",
"Edit": "Edit",
"Edit Shortcuts": "Edit Shortcuts",
"Emoji & Symbols": "Emoji & Symbols",
"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",
"Factory Reset": "Factory Reset",
"Factory Reset Data": "Factory Reset Data",
"File": "File",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
@@ -51,6 +55,9 @@
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "OR",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "Organization URL",
"Organizations": "Organizations",
"Paste": "Paste",
@@ -71,25 +80,27 @@
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"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.",
"Save": "Save",
"Select All": "Select All",
"Services": "Services",
"Settings": "Settings",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Unhide": "Unhide",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
@@ -106,29 +118,10 @@
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "About Zulip",
"Actual Size": "Actual Size",
"Add Custom Certificates": "Add Custom Certificates",
"Add Organization": "Add Organization",
"Add a Zulip organization": "Add a Zulip organization",
"Add custom CSS": "Add custom CSS",
"AddServer": "AddServer",
"Advanced": "Advanced",
"All the connected organizations will appear here": "All the connected organizations will appear here",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Always start minimized",
"App Updates": "App Updates",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "Appearance",
"Application Shortcuts": "Application Shortcuts",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "Back",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"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",
"Close": "Close",
"Connect": "Connect",
@@ -29,17 +31,19 @@
"Cut": "Cut",
"Default download location": "Default download location",
"Delete": "Delete",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Desktop Settings",
"Disconnect": "Disconnect",
"Download App Logs": "Download App Logs",
"Edit": "Edit",
"Edit Shortcuts": "Edit Shortcuts",
"Emoji & Symbols": "Emoji & Symbols",
"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",
"Factory Reset": "Factory Reset",
"Factory Reset Data": "Factory Reset Data",
"File": "File",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
@@ -51,6 +55,9 @@
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "OR",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "Organization URL",
"Organizations": "Organizations",
"Paste": "Paste",
@@ -71,25 +80,27 @@
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"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.",
"Save": "Save",
"Select All": "Select All",
"Services": "Services",
"Settings": "Settings",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Unhide": "Unhide",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
@@ -106,29 +118,10 @@
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "About Zulip",
"Actual Size": "Actual Size",
"Add Custom Certificates": "Add Custom Certificates",
"Add Organization": "Add Organization",
"Add a Zulip organization": "Add a Zulip organization",
"Add custom CSS": "Add custom CSS",
"AddServer": "AddServer",
"Advanced": "Advanced",
"All the connected organizations will appear here": "All the connected organizations will appear here",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Always start minimized",
"App Updates": "App Updates",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "Appearance",
"Application Shortcuts": "Application Shortcuts",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "Back",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"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",
"Close": "Close",
"Connect": "Connect",
@@ -29,17 +31,19 @@
"Cut": "Cut",
"Default download location": "Default download location",
"Delete": "Delete",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Desktop Settings",
"Disconnect": "Disconnect",
"Download App Logs": "Download App Logs",
"Edit": "Edit",
"Edit Shortcuts": "Edit Shortcuts",
"Emoji & Symbols": "Emoji & Symbols",
"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",
"Factory Reset": "Factory Reset",
"Factory Reset Data": "Factory Reset Data",
"File": "File",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
@@ -51,6 +55,9 @@
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "OR",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "Organization URL",
"Organizations": "Organizations",
"Paste": "Paste",
@@ -71,28 +80,27 @@
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"Report an Issue": "Report an Issue",
"Reset App Data": "Reset App Data",
"Reset App Settings": "Reset App Settings",
"Reset Application Data": "Reset Application Data",
"Reset the application, thus deleting all the connected organizations and accounts.": "Reset the application, thus deleting all the connected organizations and accounts.",
"Save": "Save",
"Select All": "Select All",
"Services": "Services",
"Settings": "Settings",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
@@ -102,6 +110,7 @@
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Unhide": "Unhide",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
@@ -109,11 +118,10 @@
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading"
"{{{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."
}

View File

@@ -1,23 +1,25 @@
{
"About Zulip": "About Zulip",
"Actual Size": "Actual Size",
"Add Custom Certificates": "Add Custom Certificates",
"Add Organization": "Add Organisation",
"Add a Zulip organization": "Add a Zulip organisation",
"Add custom CSS": "Add custom CSS",
"AddServer": "AddServer",
"Advanced": "Advanced",
"All the connected organizations will appear here": "All the connected organisations will appear here",
"All the connected organizations will appear here.": "All the connected organizations will appear here.",
"Always start minimized": "Always start minimized",
"App Updates": "App Updates",
"App language (requires restart)": "App language (requires restart)",
"Appearance": "Appearance",
"Application Shortcuts": "Application Shortcuts",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organisation?",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "Back",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"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",
"Close": "Close",
"Connect": "Connect",
@@ -29,17 +31,19 @@
"Cut": "Cut",
"Default download location": "Default download location",
"Delete": "Delete",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Desktop Settings",
"Disconnect": "Disconnect",
"Download App Logs": "Download App Logs",
"Edit": "Edit",
"Edit Shortcuts": "Edit Shortcuts",
"Emoji & Symbols": "Emoji & Symbols",
"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",
"Factory Reset": "Factory Reset",
"Factory Reset Data": "Factory Reset Data",
"File": "File",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
@@ -51,6 +55,9 @@
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Hide Zulip": "Hide Zulip",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
@@ -61,7 +68,9 @@
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"Network and Proxy Settings": "Network and Proxy Settings",
"OR": "OR",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Organization URL": "Organisation URL",
"Organizations": "Organisations",
"Paste": "Paste",
@@ -71,25 +80,27 @@
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Quit when the window is closed": "Quit when the window is closed",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"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.",
"Save": "Save",
"Select All": "Select All",
"Services": "Services",
"Settings": "Settings",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Spellchecker Languages": "Spellchecker Languages",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organisation",
"Switch to Previous Organization": "Switch to Previous Organisation",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
@@ -99,6 +110,7 @@
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Unhide": "Unhide",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
@@ -106,29 +118,10 @@
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed",
"Ask where to save files before downloading": "Ask where to save files before downloading",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"AddServer": "AddServer",
"App language (requires restart)": "App language (requires restart)",
"Factory Reset Data": "Factory Reset Data",
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organisations, accounts, and certificates.",
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
"Copy Link": "Copy Link",
"Copy Image": "Copy Image",
"Copy Image URL": "Copy Image URL",
"No Suggestion Found": "No Suggestion Found",
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
"Spellchecker Languages": "Spellchecker Languages",
"Add to Dictionary": "Add to Dictionary",
"Look Up": "Look Up"
"{{{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."
}

Some files were not shown because too many files have changed in this diff Show More