├── .eslintrc.cjs
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug-desktop.yml
│ ├── config.yml
│ └── enhancement.yml
├── PULL_REQUEST_TEMPLATE.md
├── SSLcom-sandbox.crt
├── labels.yml
├── release-drafter.yml
├── renovate.json
└── workflows
│ ├── backport.yml
│ ├── build_and_deploy.yaml
│ ├── build_and_test.yaml
│ ├── build_linux.yaml
│ ├── build_macos.yaml
│ ├── build_prepare.yaml
│ ├── build_test.yaml
│ ├── build_windows.yaml
│ ├── dockerbuild.yaml
│ ├── localazy_download.yaml
│ ├── localazy_upload.yaml
│ ├── pull_request.yaml
│ ├── release-drafter.yml
│ ├── release-gitflow.yml
│ ├── release.yml
│ ├── static_analysis.yaml
│ ├── sync-labels.yml
│ ├── triage-incoming.yml
│ ├── triage-labelled.yml
│ └── triage-stale.yml
├── .gitignore
├── .husky
└── pre-commit
├── .lintstagedrc
├── .node-version
├── .prettierignore
├── .prettierrc.cjs
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE-AGPL-3.0
├── LICENSE-COMMERCIAL
├── LICENSE-GPL-3.0
├── README.md
├── babel.config.cjs
├── build
├── entitlements.mac.plist
├── icon.icns
├── icon.ico
├── icons
│ ├── 128x128.png
│ ├── 16x16.png
│ ├── 24x24.png
│ ├── 256x256.png
│ ├── 48x48.png
│ ├── 512x512.png
│ ├── 64x64.png
│ └── 96x96.png
└── install-spinner.gif
├── dockerbuild
├── Dockerfile
└── setup.sh
├── docs
├── SUMMARY.md
├── config.md
├── debugging.md
├── gdb.md
├── native-node-modules.md
├── packaging.md
├── updates.md
└── windows-requirements.md
├── electron-builder.ts
├── element.io
├── New_Vector_Ltd.pem
├── README
├── nightly
│ └── config.json
└── release
│ └── config.json
├── hak
├── matrix-seshat
│ ├── build.ts
│ ├── check.ts
│ └── hak.json
└── tsconfig.json
├── knip.ts
├── localazy.json
├── package.json
├── playwright.config.ts
├── playwright
├── .gitignore
├── Dockerfile
├── docker-entrypoint.sh
├── e2e
│ └── launch
│ │ ├── config-options.spec.ts
│ │ ├── launch.spec.ts
│ │ └── oidc.spec.ts
├── element-desktop-test.ts
├── fixtures
│ └── custom-config.json
├── snapshots
│ └── launch
│ │ └── launch.spec.ts
│ │ └── App-launch-should-launch-and-render-the-welcome-view-successfully-1-linux.png
└── tsconfig.json
├── res
└── img
│ ├── element.ico
│ └── element.png
├── scripts
├── branch-match.sh
├── cl.bat
├── copy-res.ts
├── fetch-package.ts
├── generate-nightly-version.ts
├── get-version.ts
├── glibc-check.sh
├── hak
│ ├── README.md
│ ├── build.ts
│ ├── check.ts
│ ├── clean.ts
│ ├── copy.ts
│ ├── dep.ts
│ ├── fetch.ts
│ ├── hakEnv.ts
│ ├── index.ts
│ ├── link.ts
│ └── target.ts
├── in-docker.sh
├── set-version.ts
└── tsconfig.json
├── src
├── @types
│ ├── global.d.ts
│ └── matrix-seshat.d.ts
├── build-config.ts
├── displayMediaCallback.ts
├── electron-main.ts
├── i18n
│ └── strings
│ │ ├── cs.json
│ │ ├── cy.json
│ │ ├── de_DE.json
│ │ ├── el.json
│ │ ├── en_EN.json
│ │ ├── eo.json
│ │ ├── es.json
│ │ ├── et.json
│ │ ├── fa.json
│ │ ├── fi.json
│ │ ├── fr.json
│ │ ├── gl.json
│ │ ├── he.json
│ │ ├── hu.json
│ │ ├── id.json
│ │ ├── is.json
│ │ ├── it.json
│ │ ├── ja.json
│ │ ├── ka.json
│ │ ├── lo.json
│ │ ├── lt.json
│ │ ├── lv.json
│ │ ├── mg_MG.json
│ │ ├── nb_NO.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── pt_BR.json
│ │ ├── ru.json
│ │ ├── sk.json
│ │ ├── sq.json
│ │ ├── sv.json
│ │ ├── tr.json
│ │ ├── uk.json
│ │ ├── vi.json
│ │ ├── zh_Hans.json
│ │ └── zh_Hant.json
├── ipc.ts
├── language-helper.ts
├── macos-titlebar.ts
├── media-auth.ts
├── preload.cts
├── protocol.ts
├── seshat.ts
├── settings.ts
├── squirrelhooks.ts
├── store.ts
├── tray.ts
├── updater.ts
├── utils.ts
├── vectormenu.ts
└── webcontents-handler.ts
├── tsconfig.json
└── yarn.lock
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["matrix-org", "n"],
3 | extends: ["plugin:matrix-org/javascript"],
4 | parserOptions: {
5 | ecmaVersion: 2021,
6 | project: ["tsconfig.json"],
7 | },
8 | env: {
9 | es6: true,
10 | node: true,
11 | // we also have some browser code (ie. the preload script)
12 | browser: true,
13 | },
14 | // NOTE: These rules are frozen and new rules should not be added here.
15 | // New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/
16 | rules: {
17 | "quotes": "off",
18 | "indent": "off",
19 | "prefer-promise-reject-errors": "off",
20 | "no-async-promise-executor": "off",
21 |
22 | "n/file-extension-in-import": ["error", "always"],
23 | "unicorn/prefer-node-protocol": ["error"],
24 | },
25 | overrides: [
26 | {
27 | files: ["src/**/*.ts"],
28 | extends: ["plugin:matrix-org/typescript"],
29 | rules: {
30 | // Things we do that break the ideal style
31 | "prefer-promise-reject-errors": "off",
32 | "quotes": "off",
33 |
34 | "@typescript-eslint/no-explicit-any": "off",
35 | // We're okay with assertion errors when we ask for them
36 | "@typescript-eslint/no-non-null-assertion": "off",
37 | },
38 | },
39 | {
40 | files: ["hak/**/*.ts"],
41 | extends: ["plugin:matrix-org/typescript"],
42 | parserOptions: {
43 | project: ["hak/tsconfig.json"],
44 | },
45 | rules: {
46 | // Things we do that break the ideal style
47 | "prefer-promise-reject-errors": "off",
48 | "quotes": "off",
49 |
50 | "@typescript-eslint/no-explicit-any": "off",
51 | // We're okay with assertion errors when we ask for them
52 | "@typescript-eslint/no-non-null-assertion": "off",
53 | },
54 | },
55 | {
56 | files: ["scripts/**/*.ts"],
57 | extends: ["plugin:matrix-org/typescript"],
58 | parserOptions: {
59 | project: ["scripts/tsconfig.json"],
60 | },
61 | rules: {
62 | // Things we do that break the ideal style
63 | "prefer-promise-reject-errors": "off",
64 | "quotes": "off",
65 |
66 | "@typescript-eslint/no-explicit-any": "off",
67 | // We're okay with assertion errors when we ask for them
68 | "@typescript-eslint/no-non-null-assertion": "off",
69 | },
70 | },
71 | {
72 | files: ["playwright/**/*.ts"],
73 | extends: ["plugin:matrix-org/typescript"],
74 | parserOptions: {
75 | project: ["playwright/tsconfig.json"],
76 | },
77 | rules: {
78 | // Things we do that break the ideal style
79 | "prefer-promise-reject-errors": "off",
80 | "quotes": "off",
81 |
82 | "@typescript-eslint/no-explicit-any": "off",
83 | // We're okay with assertion errors when we ask for them
84 | "@typescript-eslint/no-non-null-assertion": "off",
85 | },
86 | },
87 | ],
88 | };
89 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @element-hq/element-web-reviewers
2 | /.github/workflows/** @element-hq/element-web-team
3 | /package.json @element-hq/element-web-team
4 | /yarn.lock @element-hq/element-web-team
5 | /src/i18n/strings
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-desktop.yml:
--------------------------------------------------------------------------------
1 | name: Bug report for the Element desktop app (not in a browser)
2 | description: File a bug report if you are using the desktop Element application.
3 | labels: [T-Defect]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for taking the time to fill out this bug report!
9 |
10 | Please report security issues by email to security@matrix.org
11 | - type: textarea
12 | id: reproduction-steps
13 | attributes:
14 | label: Steps to reproduce
15 | description: Please attach screenshots, videos or logs if you can.
16 | placeholder: Tell us what you see!
17 | value: |
18 | 1. Where are you starting? What can you see?
19 | 2. What do you click?
20 | 3. More steps…
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: result
25 | attributes:
26 | label: Outcome
27 | placeholder: Tell us what went wrong
28 | value: |
29 | #### What did you expect?
30 |
31 | #### What happened instead?
32 | validations:
33 | required: true
34 | - type: input
35 | id: os
36 | attributes:
37 | label: Operating system
38 | placeholder: Windows, macOS, Ubuntu, Arch Linux…
39 | validations:
40 | required: false
41 | - type: input
42 | id: version
43 | attributes:
44 | label: Application version
45 | description: You can find the version information in Settings -> Help & About.
46 | placeholder: e.g. Element version 1.7.34, olm version 3.2.3
47 | validations:
48 | required: false
49 | - type: input
50 | id: source
51 | attributes:
52 | label: How did you install the app?
53 | description: Where did you install the app from? Please give a link or a description.
54 | placeholder: e.g. From https://element.io/get-started
55 | validations:
56 | required: false
57 | - type: input
58 | id: homeserver
59 | attributes:
60 | label: Homeserver
61 | description: |
62 | Which server is your account registered on? If it is a local or non-public homeserver, please tell us what is the homeserver implementation (ex: Synapse/Dendrite/etc.) and the version.
63 | placeholder: e.g. matrix.org or Synapse 1.50.0rc1
64 | validations:
65 | required: false
66 | - type: dropdown
67 | id: rageshake
68 | attributes:
69 | label: Will you send logs?
70 | description: |
71 | Did you know that you can send a /rageshake command from your application to submit logs for this issue? Trigger the defect, then type `/rageshake` into the message input area followed by a description of the problem and send the command. You will be able to add a link to this defect report and submit anonymous logs to the developers.
72 | options:
73 | - "Yes"
74 | - "No"
75 | validations:
76 | required: true
77 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Bug report for the Element flatpak app
4 | url: https://github.com/flathub/im.riot.Riot/issues
5 | about: Please file bugs with the Flatpak application on the respective repository.
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/enhancement.yml:
--------------------------------------------------------------------------------
1 | name: Enhancement request
2 | description: Do you have a suggestion or feature request?
3 | labels: [T-Enhancement]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion here](https://github.com/vector-im/element-meta/discussions/new?category=ideas).
9 | - type: textarea
10 | id: usecase
11 | attributes:
12 | label: Your use case
13 | description: What would you like to be able to do? Please feel welcome to include screenshots or mock ups.
14 | placeholder: Tell us what you would like to do!
15 | value: |
16 | #### What would you like to do?
17 |
18 | #### Why would you like to do it?
19 |
20 | #### How would you like to achieve it?
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: alternative
25 | attributes:
26 | label: Have you considered any alternatives?
27 | placeholder: A clear and concise description of any alternative solutions or features you've considered.
28 | validations:
29 | required: false
30 | - type: textarea
31 | id: additional-context
32 | attributes:
33 | label: Additional context
34 | placeholder: Is there anything else you'd like to add?
35 | validations:
36 | required: false
37 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Checklist
4 |
5 | - [ ] Ensure your code works with manual testing.
6 | - [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation.
7 | - [ ] Linter and other CI checks pass.
8 | - [ ] I have licensed the changes to Element by completing the [Contributor License Agreement (CLA)](https://cla-assistant.io/element-hq/element-desktop)
9 |
--------------------------------------------------------------------------------
/.github/SSLcom-sandbox.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIGBzCCA++gAwIBAgIIaI6ivggL++4wDQYJKoZIhvcNAQELBQAwgZAxCzAJBgNV
3 | BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE
4 | CgwPU1NMIENvcnBvcmF0aW9uMUUwQwYDVQQDDDxTU0wuY29tIEVWIFJvb3QgQ2Vy
5 | dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyIC0gRGV2ZWxvcG1lbnQwHhcNMTgw
6 | MTE2MTIxNjM2WhcNNDMwMTE1MTIxNjM2WjCBkDELMAkGA1UEBhMCVVMxDjAMBgNV
7 | BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9y
8 | YXRpb24xRTBDBgNVBAMMPFNTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
9 | dGhvcml0eSBSU0EgUjIgLSBEZXZlbG9wbWVudDCCAiIwDQYJKoZIhvcNAQEBBQAD
10 | ggIPADCCAgoCggIBAK/qcD65JCkueKp0+KXG2kAw8euDHuraLR3lJoUFz4ilGK1M
11 | t+RjSuY6dHQw8ku7TnW9ejWoSFjCBSDx7tP/fzOwOxmBW6+F1NDuV/IaUtn3G2lk
12 | CZglVk9z3n1HuWDN10xNiLoo5nzeIlvNAoDbXDGhI4Y6Z0qouAIS607JpJMWHOqZ
13 | OUiiOuM11gI5Kz9GtVttXCjRmwlkU8WiJVIUuVedQAQt2FChrzNQewGFFi0uIau/
14 | wFRclx6hd4JRIImC6VMJd9lcitWsqMcM94pD3fX2ozNgWX+MVlmcDYFSN9Sv8tG4
15 | yCj4ONS8HZGzbxeyQXJhEJSi2FnBi0j6MD/d4DNFj0hCg9wz3fgVLDGCO0pNMO0Y
16 | oXdrzfoj1/zEv0Ibgh7zKG2JHkPfapn3ExFI5d6xi66u5tPVI8cvLxqrgybRPs7Z
17 | y1dQA7ew3LyTPAHoGtbTMvewtx1TkTtRxxhRRm0l58owqSVbSYrixFtosNobCERo
18 | uiknaQqoY1ZDsdKsaqFoZDbntNRYhN3Ea4OPWVqDUU5ZPz9MTIRAi3MIq854yyQo
19 | BjX9nv+kYa+Esr19pxUW0z7BWFhbXsMVpt0QMVyhwgzXvEreaZHFwHHaGb9d5x5P
20 | VBDhsigMmtzBk9NlbCsy+uGXWHgZA/DVefueEq0sv38VoU30uYa5Tj0FLm09AgMB
21 | AAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUI9PCucv3G9fRoTDu
22 | ZQ4Hw6g4PkIwHQYDVR0OBBYEFCPTwrnL9xvX0aEw7mUOB8OoOD5CMA4GA1UdDwEB
23 | /wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAk43CCrC0Zbvi7YUsSePKi+KzvyQ9
24 | mjKa5NBU/A5/sLeZS3R+wqCX7l5euYVDsUuNgNVD/QL9jNIonuHBrvKaxkmqxE1r
25 | IcDEaUdjy2lQ0uqD7UDoS3ctrjGkPpUahrTdr3gaKcQBtUhn9v4Y2OBm6J1hDVwI
26 | CIKcxIzRv6AUpApOtk+++m5tzDU48t8+GzrVl1hkspSYcumA+zuHllbPDL1ADdo5
27 | kK/bBQtZrGqzPqKzeqaB1A5Wm0Igwf++7nyzdKNdjxtv907D9vg8EB4Swavuv/Ne
28 | 5/jbpI32pz0NIzzSl5ARAHuFhILsO/cEAlloDoTHzibHqFDIeU9/59HMUsJYMOtD
29 | Ii0/LmQ6dBE4TeukCCLJwtkFYZ2eBgDjF/LHBB+z/UBs4milRgwx+Pe5UDUEjtGe
30 | G/XMVnTSKZTy9jMaXJD5EmfP+Cfh8EEgFgjg4AmLUbEo9gXzPxyXSLgd8JGSsjg8
31 | EV/Ri4Mmmt4XUwlSVvEOezxxDGd17gwbottCIC+rqPHonHkGmKpLMH80Bk0uOOCs
32 | ui1oVwSifMyIcudgCcOfRLUf/f2j2NW7N7E7Vw/Zqfn+pqp/EG0KCqOM2vfJAc0s
33 | u3rSrOJZGtB6txgtmTjoadxApWf4U/FCi3uArt6gS5MJqZjuiRNXs/K3SlSAqLGl
34 | 5UiG52ew+VdBHzE=
35 | -----END CERTIFICATE-----
36 |
--------------------------------------------------------------------------------
/.github/labels.yml:
--------------------------------------------------------------------------------
1 | - name: "A-Install"
2 | color: "72A447"
3 | - name: "A-Seshat"
4 | color: "8262BE"
5 | - name: "A-Update"
6 | color: "17BE67"
7 | - name: "Story"
8 | description: "A change to the product that generates user value on its own. Unit of delivery."
9 | color: "0BAC47"
10 | - name: "X-Breaking-Change"
11 | color: "ff7979"
12 | - name: "Z-Arch"
13 | color: "D601BE"
14 | - name: "Z-ARM"
15 | color: "5DEC5B"
16 | - name: "Z-Flatpak"
17 | color: "0CA856"
18 | - name: "Z-Linux"
19 | color: "7B4A9C"
20 | - name: "Z-macOS"
21 | color: "500605"
22 | - name: "Z-Official"
23 | color: "1D2B20"
24 | - name: "Z-Snap"
25 | color: "29CD95"
26 | - name: "Z-Suse"
27 | color: "79D07B"
28 | - name: "Z-Wayland"
29 | color: "94C519"
30 | - name: "Z-Windows"
31 | color: "0632DE"
32 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | _extends: element-hq/element-web
2 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["github>matrix-org/renovate-config-element-web"]
4 | }
5 |
--------------------------------------------------------------------------------
/.github/workflows/backport.yml:
--------------------------------------------------------------------------------
1 | name: Backport
2 | on:
3 | pull_request_target:
4 | types:
5 | - closed
6 | - labeled
7 | branches:
8 | - develop
9 |
10 | permissions: {} # We use ELEMENT_BOT_TOKEN instead
11 |
12 | jobs:
13 | backport:
14 | name: Backport
15 | runs-on: ubuntu-24.04
16 | # Only react to merged PRs for security reasons.
17 | # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
18 | if: >
19 | github.event.pull_request.merged
20 | && (
21 | github.event.action == 'closed'
22 | || (
23 | github.event.action == 'labeled'
24 | && contains(github.event.label.name, 'backport')
25 | )
26 | )
27 | steps:
28 | - uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2
29 | with:
30 | labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
31 | # We can't use GITHUB_TOKEN here or CI won't run on the new PR
32 | github_token: ${{ secrets.ELEMENT_BOT_TOKEN }}
33 |
--------------------------------------------------------------------------------
/.github/workflows/build_and_test.yaml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 | on:
3 | pull_request: {}
4 | push:
5 | branches: [develop, staging, master]
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.ref }}
8 | cancel-in-progress: true
9 | permissions: {} # No permissions required
10 | jobs:
11 | fetch:
12 | uses: ./.github/workflows/build_prepare.yaml
13 | permissions:
14 | contents: read
15 | with:
16 | config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }}
17 | version: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'develop' || '' }}
18 | branch-matching: true
19 |
20 | windows:
21 | needs: fetch
22 | name: Windows
23 | uses: ./.github/workflows/build_windows.yaml
24 | strategy:
25 | matrix:
26 | arch: [x64, ia32, arm64]
27 | with:
28 | arch: ${{ matrix.arch }}
29 | blob_report: true
30 |
31 | linux:
32 | needs: fetch
33 | name: "Linux (${{ matrix.arch }}) (sqlcipher: ${{ matrix.sqlcipher }})"
34 | uses: ./.github/workflows/build_linux.yaml
35 | strategy:
36 | matrix:
37 | sqlcipher: [system, static]
38 | arch: [amd64, arm64]
39 | with:
40 | sqlcipher: ${{ matrix.sqlcipher }}
41 | arch: ${{ matrix.arch }}
42 | blob_report: true
43 |
44 | macos:
45 | needs: fetch
46 | name: macOS
47 | uses: ./.github/workflows/build_macos.yaml
48 | with:
49 | blob_report: true
50 |
51 | tests-done:
52 | needs: [windows, linux, macos]
53 | runs-on: ubuntu-24.04
54 | if: always()
55 | steps:
56 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
57 |
58 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
59 | with:
60 | cache: "yarn"
61 | node-version: "lts/*"
62 |
63 | - name: Install dependencies
64 | run: yarn install --frozen-lockfile
65 |
66 | - name: Download blob reports from GitHub Actions Artifacts
67 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
68 | with:
69 | pattern: blob-report-*
70 | path: all-blob-reports
71 | merge-multiple: true
72 |
73 | - name: Merge into HTML Report
74 | run: yarn playwright merge-reports -c ./playwright.config.ts --reporter=html ./all-blob-reports
75 |
76 | - name: Upload HTML report
77 | if: always()
78 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
79 | with:
80 | name: html-report
81 | path: playwright-report
82 | retention-days: 14
83 |
84 | - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
85 | run: exit 1
86 |
--------------------------------------------------------------------------------
/.github/workflows/build_test.yaml:
--------------------------------------------------------------------------------
1 | # This action helps run Playwright tests within one of the build_* stages.
2 | on:
3 | workflow_call:
4 | inputs:
5 | runs-on:
6 | type: string
7 | required: true
8 | description: "The runner image to use"
9 | artifact:
10 | type: string
11 | required: true
12 | description: "The name of the artifact to download"
13 | executable:
14 | type: string
15 | required: true
16 | description: "Path to the executable to test"
17 | prepare_cmd:
18 | type: string
19 | required: false
20 | description: "Command to run to prepare the executable or environment for testing"
21 | blob_report:
22 | type: boolean
23 | default: false
24 | description: "Whether to upload a blob report instead of the HTML report"
25 | permissions: {}
26 | jobs:
27 | test:
28 | runs-on: ${{ inputs.runs-on }}
29 | steps:
30 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
31 |
32 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
33 | with:
34 | node-version-file: .node-version
35 | cache: "yarn"
36 |
37 | - name: Install Deps
38 | run: "yarn install --frozen-lockfile"
39 |
40 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
41 | with:
42 | name: ${{ inputs.artifact }}
43 | path: dist
44 |
45 | - name: Prepare for tests
46 | run: ${{ inputs.prepare_cmd }}
47 | if: inputs.prepare_cmd
48 |
49 | - name: Expand executable path
50 | id: executable
51 | shell: bash
52 | env:
53 | EXECUTABLE: ${{ inputs.executable }}
54 | run: |
55 | FILES=($EXECUTABLE)
56 | echo "path=${FILES[0]}" >> $GITHUB_OUTPUT
57 |
58 | # We previously disabled the `EnableNodeCliInspectArguments` fuse, but Playwright requires
59 | # it to be enabled to test Electron apps, so turn it back on.
60 | - name: Set EnableNodeCliInspectArguments fuse enabled
61 | run: $RUN_AS npx @electron/fuses write --app "$EXECUTABLE" EnableNodeCliInspectArguments=on
62 | shell: bash
63 | env:
64 | # We need sudo on Linux as it is installed in /opt/
65 | RUN_AS: ${{ runner.os == 'Linux' && 'sudo' || '' }}
66 | EXECUTABLE: ${{ steps.executable.outputs.path }}
67 |
68 | - name: Run tests
69 | uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a
70 | timeout-minutes: 20
71 | with:
72 | run: yarn test --project=${{ inputs.artifact }} ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} ${{ inputs.blob_report == false && '--reporter=html' || '' }}
73 | env:
74 | ELEMENT_DESKTOP_EXECUTABLE: ${{ steps.executable.outputs.path }}
75 |
76 | - name: Upload blob report
77 | if: always() && inputs.blob_report
78 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
79 | with:
80 | name: blob-report-${{ inputs.artifact }}
81 | path: blob-report
82 | retention-days: 1
83 |
84 | - name: Upload HTML report
85 | if: always() && inputs.blob_report == false
86 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
87 | with:
88 | name: ${{ inputs.artifact }}-test
89 | path: playwright-report
90 | retention-days: 14
91 |
--------------------------------------------------------------------------------
/.github/workflows/dockerbuild.yaml:
--------------------------------------------------------------------------------
1 | name: Dockerbuild
2 | on:
3 | workflow_dispatch: {}
4 | push:
5 | branches: [master, staging, develop]
6 | paths:
7 | - "dockerbuild/**"
8 | pull_request:
9 | concurrency: ${{ github.workflow }}-${{ github.ref_name }}
10 | env:
11 | REGISTRY: ghcr.io
12 | IMAGE_NAME: ${{ github.repository }}-dockerbuild
13 | permissions: {}
14 | jobs:
15 | build:
16 | name: Docker Build
17 | runs-on: ubuntu-24.04
18 | permissions:
19 | contents: read
20 | packages: write
21 | steps:
22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
23 |
24 | - name: Set up QEMU
25 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
26 |
27 | - name: Set up Docker Buildx
28 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
29 | with:
30 | install: true
31 |
32 | - name: Build test image
33 | uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
34 | with:
35 | file: dockerbuild/Dockerfile
36 | push: false
37 | load: true
38 | tags: element-desktop-dockerbuild
39 | platforms: linux/amd64
40 |
41 | - name: Test image
42 | run: docker run -v $PWD:/project element-desktop-dockerbuild yarn install
43 |
44 | - name: Log in to the Container registry
45 | uses: docker/login-action@6d4b68b490aef8836e8fb5e50ee7b3bdfa5894f0
46 | if: github.event_name != 'pull_request'
47 | with:
48 | registry: ${{ env.REGISTRY }}
49 | username: ${{ github.actor }}
50 | password: ${{ secrets.GITHUB_TOKEN }}
51 |
52 | - name: Extract metadata for Docker
53 | id: meta
54 | if: github.event_name != 'pull_request'
55 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
56 | with:
57 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
58 | tags: |
59 | type=ref,event=branch
60 | type=ref,event=pr
61 |
62 | - name: Build and push Docker image
63 | if: github.event_name != 'pull_request'
64 | uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
65 | with:
66 | file: dockerbuild/Dockerfile
67 | push: true
68 | tags: ${{ steps.meta.outputs.tags }}
69 | labels: ${{ steps.meta.outputs.labels }}
70 | platforms: linux/amd64,linux/arm64
71 |
--------------------------------------------------------------------------------
/.github/workflows/localazy_download.yaml:
--------------------------------------------------------------------------------
1 | name: Localazy Download
2 | on:
3 | workflow_dispatch: {}
4 | schedule:
5 | - cron: "0 6 * * 1,3,5" # Every Monday, Wednesday and Friday at 6am UTC
6 | permissions:
7 | pull-requests: write # needed to auto-approve PRs
8 | jobs:
9 | download:
10 | uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_download.yaml@main
11 | secrets:
12 | ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
13 |
--------------------------------------------------------------------------------
/.github/workflows/localazy_upload.yaml:
--------------------------------------------------------------------------------
1 | name: Localazy Upload
2 | on:
3 | push:
4 | branches: [develop]
5 | paths:
6 | - "src/i18n/strings/en_EN.json"
7 | permissions: {} # No permissions needed
8 | jobs:
9 | upload:
10 | uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_upload.yaml@main
11 | secrets:
12 | LOCALAZY_WRITE_KEY: ${{ secrets.LOCALAZY_WRITE_KEY }}
13 |
--------------------------------------------------------------------------------
/.github/workflows/pull_request.yaml:
--------------------------------------------------------------------------------
1 | name: Pull Request
2 | on:
3 | pull_request_target:
4 | types: [opened, edited, labeled, unlabeled, synchronize]
5 | permissions: {}
6 | jobs:
7 | action:
8 | uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
9 | permissions:
10 | pull-requests: write
11 | secrets:
12 | ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
13 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 | on:
3 | push:
4 | branches: [staging]
5 | workflow_dispatch: {}
6 | concurrency: ${{ github.workflow }}
7 | permissions: {}
8 | jobs:
9 | draft:
10 | uses: matrix-org/matrix-js-sdk/.github/workflows/release-drafter-workflow.yml@develop
11 | permissions:
12 | contents: write
13 | with:
14 | include-changes: element-hq/element-web~$VERSION
15 |
--------------------------------------------------------------------------------
/.github/workflows/release-gitflow.yml:
--------------------------------------------------------------------------------
1 | # Gitflow merge-back master->develop
2 | name: Merge master -> develop
3 | on:
4 | push:
5 | branches: [master]
6 | concurrency: ${{ github.repository }}-${{ github.workflow }}
7 | permissions: {} # Uses ELEMENT_BOT_TOKEN
8 | jobs:
9 | merge:
10 | uses: matrix-org/matrix-js-sdk/.github/workflows/release-gitflow.yml@develop
11 | secrets:
12 | ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
13 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Process
2 | on:
3 | workflow_dispatch:
4 | inputs:
5 | mode:
6 | description: What type of release
7 | required: true
8 | default: rc
9 | type: choice
10 | options:
11 | - rc
12 | - final
13 | concurrency: ${{ github.workflow }}
14 | permissions: {}
15 | jobs:
16 | release:
17 | uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop
18 | permissions:
19 | contents: write
20 | issues: write
21 | pull-requests: read
22 | id-token: write
23 | secrets:
24 | ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
25 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
26 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
27 | with:
28 | final: ${{ inputs.mode == 'final' }}
29 | gpg-fingerprint: ${{ vars.GPG_FINGERPRINT }}
30 | expected-asset-count: 1
31 |
32 | check:
33 | name: Post release checks
34 | needs: release
35 | runs-on: ubuntu-24.04
36 | permissions:
37 | checks: read
38 | steps:
39 | - name: Wait for desktop packaging
40 | uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
41 | with:
42 | ref: master
43 | repo-token: ${{ secrets.GITHUB_TOKEN }}
44 | wait-interval: 10
45 | check-regexp: Prepare|Linux|macOS|Windows|Deploy|deploy
46 | allowed-conclusions: success
47 |
--------------------------------------------------------------------------------
/.github/workflows/static_analysis.yaml:
--------------------------------------------------------------------------------
1 | name: Static Analysis
2 | on:
3 | pull_request: {}
4 | push:
5 | branches: [develop, master]
6 | permissions: {} # No permissions needed
7 | jobs:
8 | ts_lint:
9 | name: "Typescript Syntax Check"
10 | runs-on: ubuntu-24.04
11 | steps:
12 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
13 |
14 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
15 | with:
16 | node-version-file: package.json
17 | cache: "yarn"
18 |
19 | # Does not need branch matching as only analyses this layer
20 | - name: Install Deps
21 | run: "yarn install --frozen-lockfile"
22 |
23 | - name: Typecheck
24 | run: "yarn run lint:types"
25 |
26 | i18n_lint:
27 | name: "i18n Check"
28 | uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
29 | permissions:
30 | pull-requests: read
31 | with:
32 | hardcoded-words: "Element"
33 |
34 | js_lint:
35 | name: "ESLint"
36 | runs-on: ubuntu-24.04
37 | steps:
38 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
39 |
40 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
41 | with:
42 | node-version-file: package.json
43 | cache: "yarn"
44 |
45 | # Does not need branch matching as only analyses this layer
46 | - name: Install Deps
47 | run: "yarn install --frozen-lockfile"
48 |
49 | - name: Run Linter
50 | run: "yarn run lint:js"
51 |
52 | workflow_lint:
53 | name: "Workflow Lint"
54 | runs-on: ubuntu-24.04
55 | steps:
56 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
57 |
58 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
59 | with:
60 | node-version-file: package.json
61 | cache: "yarn"
62 |
63 | # Does not need branch matching as only analyses this layer
64 | - name: Install Deps
65 | run: "yarn install --frozen-lockfile"
66 |
67 | - name: Run Linter
68 | run: "yarn lint:workflows"
69 |
70 | analyse_dead_code:
71 | name: "Analyse Dead Code"
72 | runs-on: ubuntu-24.04
73 | steps:
74 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
75 |
76 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
77 | with:
78 | node-version-file: package.json
79 | cache: "yarn"
80 |
81 | - name: Install Deps
82 | run: "yarn install --frozen-lockfile"
83 |
84 | - name: Run linter
85 | run: "yarn run lint:knip"
86 |
--------------------------------------------------------------------------------
/.github/workflows/sync-labels.yml:
--------------------------------------------------------------------------------
1 | name: Sync labels
2 | on:
3 | workflow_dispatch: {}
4 | schedule:
5 | - cron: "0 2 * * *" # 2am every day
6 | push:
7 | branches:
8 | - develop
9 | paths:
10 | - .github/labels.yml
11 | permissions: {} # Uses ELEMENT_BOT_TOKEN
12 | jobs:
13 | sync-labels:
14 | uses: element-hq/element-meta/.github/workflows/sync-labels.yml@develop
15 | with:
16 | LABELS: |
17 | element-hq/element-web
18 | .github/labels.yml
19 | DELETE: true
20 | WET: true
21 | secrets:
22 | ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/triage-incoming.yml:
--------------------------------------------------------------------------------
1 | name: Move new issues into Issue triage board
2 |
3 | on:
4 | issues:
5 | types: [opened]
6 |
7 | permissions: {} # Uses ELEMENT_BOT_TOKEN
8 |
9 | jobs:
10 | automate-project-columns-next:
11 | runs-on: ubuntu-24.04
12 | steps:
13 | - uses: actions/add-to-project@main
14 | with:
15 | project-url: https://github.com/orgs/element-hq/projects/120
16 | github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
17 |
--------------------------------------------------------------------------------
/.github/workflows/triage-labelled.yml:
--------------------------------------------------------------------------------
1 | name: Move labelled issues to correct projects
2 |
3 | on:
4 | issues:
5 | types: [labeled]
6 |
7 | permissions: {} # Uses ELEMENT_BOT_TOKEN
8 |
9 | jobs:
10 | call-triage-labelled:
11 | uses: element-hq/element-web/.github/workflows/triage-labelled.yml@develop
12 | secrets: inherit
13 |
--------------------------------------------------------------------------------
/.github/workflows/triage-stale.yml:
--------------------------------------------------------------------------------
1 | name: Close stale PRs
2 | on:
3 | workflow_dispatch: {}
4 | schedule:
5 | - cron: "30 1 * * *"
6 | permissions: {}
7 | jobs:
8 | close:
9 | runs-on: ubuntu-24.04
10 | permissions:
11 | actions: write
12 | issues: write
13 | pull-requests: write
14 | steps:
15 | - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
16 | with:
17 | operations-per-run: 250
18 | days-before-issue-stale: -1
19 | days-before-issue-close: -1
20 | days-before-pr-stale: 180
21 | days-before-pr-close: 0
22 | close-pr-message: "This PR has been automatically closed because it has been stale for 180 days. If you wish to continue working on this PR, please ping a maintainer to reopen it."
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /lib
3 | /webapp
4 | /webapp.asar
5 | /packages
6 | /deploys
7 | node_modules/
8 | /pkg/control
9 | /.hak
10 | /.yarnrc
11 | /docker
12 | /.npmrc
13 | .vscode
14 | .vscode/
15 | /test_artifacts/
16 | /coverage/
17 | yarn-error.log
18 | /hak/**/*.js
19 | /scripts/hak/**/*.js
20 | .DS_Store
21 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged --concurrent false
5 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*": "prettier --write",
3 | "*.(ts|tsx)": ["eslint --fix"]
4 | }
5 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | v22.16.0
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /dockerbuild/
3 | /lib/
4 | /node_modules/
5 | /packages.elememt.io/
6 | /webapp
7 | /src/i18n/strings
8 | /CHANGELOG.md
9 | /package-lock.json
10 | /yarn.lock
11 | /playwright/html-report
12 | /playwright/test-results
13 |
14 | **/.idea
15 | .vscode
16 | .vscode/
17 | .tmp
18 | .env
19 | /coverage
20 | /.npmrc
21 | /*.log
22 |
--------------------------------------------------------------------------------
/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = require("eslint-plugin-matrix-org/.prettierrc.js");
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing code to element-desktop
2 |
3 | Everyone is welcome to contribute code to element-desktop, provided that they are willing to license their contributions to Element under a [Contributor License Agreement](https://cla-assistant.io/element-hq/element-desktop) (CLA). This ensures that their contribution will be made available under an approved licence(as described in the [README](/README.md#copyright--license)).
4 |
5 | element-desktop follows the same pattern as element-web, please find more contributing guidelines at https://github.com/vector-im/element-web/blob/develop/CONTRIBUTING.md
6 |
--------------------------------------------------------------------------------
/LICENSE-COMMERCIAL:
--------------------------------------------------------------------------------
1 | Licensees holding a valid commercial license with Element may use this
2 | software in accordance with the terms contained in a written agreement
3 | between you and Element.
4 |
5 | To purchase a commercial license please contact our sales team at
6 | licensing@element.io
--------------------------------------------------------------------------------
/babel.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
3 | };
4 |
--------------------------------------------------------------------------------
/build/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 | com.apple.security.cs.allow-jit
15 |
16 |
17 |
18 | com.apple.security.cs.disable-library-validation
19 |
20 |
21 |
23 | com.apple.security.device.camera
24 |
25 | com.apple.security.device.audio-input
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/build/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icon.icns
--------------------------------------------------------------------------------
/build/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icon.ico
--------------------------------------------------------------------------------
/build/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/128x128.png
--------------------------------------------------------------------------------
/build/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/16x16.png
--------------------------------------------------------------------------------
/build/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/24x24.png
--------------------------------------------------------------------------------
/build/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/256x256.png
--------------------------------------------------------------------------------
/build/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/48x48.png
--------------------------------------------------------------------------------
/build/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/512x512.png
--------------------------------------------------------------------------------
/build/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/64x64.png
--------------------------------------------------------------------------------
/build/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/icons/96x96.png
--------------------------------------------------------------------------------
/build/install-spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/build/install-spinner.gif
--------------------------------------------------------------------------------
/dockerbuild/Dockerfile:
--------------------------------------------------------------------------------
1 | # Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31)
2 | # with broader compatibility, down to Debian bullseye & Ubuntu focal.
3 | FROM rust:bullseye@sha256:eb809362961259a30f540857c3cac8423c466d558bea0f55f32e3a6354654353
4 |
5 | ENV DEBIAN_FRONTEND=noninteractive
6 |
7 | RUN curl --proto "=https" -L https://yarnpkg.com/latest.tar.gz | tar xvz && mv yarn-* /yarn && ln -s /yarn/bin/yarn /usr/bin/yarn
8 | RUN apt-get -qq update && apt-get -y -qq dist-upgrade && \
9 | apt-get -y -qq install --no-install-recommends \
10 | # tclsh is required for building SQLite as part of SQLCipher
11 | tcl \
12 | # libsecret-1-dev is required even for prebuild keytar
13 | libsecret-1-dev \
14 | # Used by seshat (when not SQLCIPHER_STATIC) \
15 | libsqlcipher-dev && \
16 | apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/*
17 | RUN ln -s /usr/bin/python3 /usr/bin/python & ln -s /usr/bin/pip3 /usr/bin/pip
18 |
19 | ENV DEBUG_COLORS=true
20 | ENV FORCE_COLOR=true
21 |
22 | WORKDIR /project
23 |
24 | ARG TARGETOS
25 | ARG TARGETARCH
26 | COPY .node-version dockerbuild/setup.sh /
27 | RUN /setup.sh
28 |
--------------------------------------------------------------------------------
/dockerbuild/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -x
4 | declare -A archMap=(["amd64"]="x64" ["arm64"]="arm64")
5 | ARCH="${archMap["$TARGETARCH"]}"
6 | NODE_VERSION=$(cat /.node-version)
7 | curl --proto "=https" -L "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-$TARGETOS-$ARCH.tar.gz" | tar xz -C /usr/local --strip-components=1 && \
8 | unlink /usr/local/CHANGELOG.md && unlink /usr/local/LICENSE && unlink /usr/local/README.md
9 |
--------------------------------------------------------------------------------
/docs/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | - [Introduction](../README.md)
4 |
5 | # Build/Debug
6 |
7 | - [Native Node modules](native-node-modules.md)
8 | - [Windows requirements](windows-requirements.md)
9 | - [Debugging](debugging.md)
10 | - [Using gdb](gdb.md)
11 |
12 | # Distribution
13 |
14 | - [Updates](updates.md)
15 | - [Packaging](packaging.md)
16 |
17 | # Setup
18 |
19 | - [Config](config.md)
20 |
--------------------------------------------------------------------------------
/docs/config.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | All Element Web options documented [here](https://github.com/vector-im/element-web/blob/develop/docs/config.md) can be used as well as the following:
4 |
5 | ---
6 |
7 | The app contains a configuration file specified at build time using [these instructions](https://github.com/vector-im/element-desktop/#config).
8 | This config can be overwritten by the end using by creating a `config.json` file at the paths described [here](https://github.com/vector-im/element-desktop/#user-specified-configjson).
9 |
10 | After changing the config, the app will need to be exited fully (including via the task tray) and re-started.
11 |
12 | ---
13 |
14 | 1. `update_base_url`: Specifies the URL of the update server, see [document](https://github.com/vector-im/element-desktop/blob/develop/docs/updates.md).
15 | 2. `web_base_url`: Specifies the Element Web URL when performing actions such as popout widget. Defaults to `https://app.element.io/`.
16 |
--------------------------------------------------------------------------------
/docs/debugging.md:
--------------------------------------------------------------------------------
1 | # Debugging Element-Desktop
2 |
3 | There are two parts of the desktop app that you might want to debug.
4 |
5 | ## The renderer process
6 |
7 | This is the regular element-web codeand can be debugged by just selecting 'toggle developer tools'
8 | from the menu, even on ppackaged builds. This then works the same as chrome dev tools for element web.
9 |
10 | ## The main process
11 |
12 | This is debugged as a node app, so:
13 |
14 | 1. Open any chrome dev tools window
15 | 1. Start element with the `--inspect-brk` flag
16 | 1. Notice that you now have a little green icon in the top left of your chrome devtools window, click it.
17 |
18 | You are now debugging the code of the desktop app itself.
19 |
20 | ## The main process of a package app
21 |
22 | When the app is shipped, electron's "fuses" are flipped, editing the electron binary itself to prevent certain features from being usable, one of which is debugging using `--inspect-brk` as above. You can flip the fuse back on Linux as follows:
23 |
24 | ```
25 | sudo npx @electron/fuses write --app /opt/Element/element-desktop EnableNodeCliInspectArguments=on
26 | ```
27 |
28 | A similar command will work, in theory, on mac and windows, except that this will break code signing (which is the point of fuses) so you would have to re-sign the app or somesuch.
29 |
--------------------------------------------------------------------------------
/docs/gdb.md:
--------------------------------------------------------------------------------
1 | # Using gdb against Element-Desktop
2 |
3 | Occasionally it is useful to be able to connect to a running Element-Desktop
4 | with [`gdb`](https://sourceware.org/gdb/), or to analayze a coredump. For this,
5 | you will need debug symbols.
6 |
7 | 1. If you don't already have the right version of Element-Desktop (eg because
8 | you are analyzing someone else's coredump), download and unpack the tarball
9 | from https://packages.element.io/desktop/install/linux/. If it was a
10 | nightly, your best bet may be to download the deb from
11 | https://packages.element.io/debian/pool/main/e/element-nightly/ and unpack
12 | it.
13 | 2. Figure out which version of Electron your Element-Desktop is based on. The
14 | best way to do this is to figure out the version of Element-Desktop, then
15 | look at
16 | [`yarn.lock`](https://github.com/element-hq/element-desktop/blob/develop/yarn.lock)
17 | for the corresponding version. There should be an entry starting
18 | `electron@`, and under it a `version` line: this will tell you the version
19 | of Electron that was used for that version of Element-Desktop.
20 |
21 | 3. Go to [Electron's releases page](https://github.com/electron/electron/releases/)
22 | and find the version you just identified. Under "Assets", download
23 | `electron-v-linux-x64-debug.zip` (or, the -debug zip corresponding to your
24 | architecture).
25 |
26 | 4. The debug zip has a structure like:
27 |
28 | ```
29 | .
30 | ├── debug
31 | │ ├── chrome_crashpad_handler.debug
32 | │ ├── electron.debug
33 | │ ├── libEGL.so.debug
34 | │ ├── libffmpeg.so.debug
35 | │ ├── libGLESv2.so.debug
36 | │ └── libvk_swiftshader.so.debug
37 | ├── LICENSE
38 | ├── LICENSES.chromium.html
39 | └── version
40 | ```
41 |
42 | Take all the contents of `debug`, and copy them into the Element-Desktop directory,
43 | so that `electron.debug` is alongside the `element-desktop-nightly` executable.
44 |
45 | 5. You now have a thing you can gdb as normal, either as `gdb --args element-desktop-nightly`, or
46 | `gdb element-desktop-nightly core`.
47 |
--------------------------------------------------------------------------------
/docs/packaging.md:
--------------------------------------------------------------------------------
1 | ## Packaging nightlies
2 |
3 | Element Desktop nightly builds are build automatically by the [Github Actions workflow](https://github.com/vector-im/element-desktop/blob/develop/.github/workflows/build_and_deploy.yaml).
4 | The schedule is currently set for once a day at 9am UTC. It will deploy to packages.element.io upon completion.
5 |
6 | ## Triggering a manual nightly build
7 |
8 | Simply go to https://github.com/vector-im/element-desktop/actions/workflows/build_and_deploy.yaml
9 |
10 | 1. Click `Run workflow`
11 | 1. Feel free to make changes to the checkboxes depending on the circumstances
12 | 1. Click the green `Run workflow`
13 |
14 | ## Packaging releases
15 |
16 | **Don't do this for RCs! We don't build Element Desktop for RCs.**
17 |
18 | For releasing Element Desktop, we assume the following prerequisites:
19 |
20 | - a tag of `element-desktop` repo with the Element Desktop version to be released set in `package.json`.
21 | - an Element Web tarball published to GitHub with a matching version number.
22 |
23 | **Both of these are done automatically when you run the release automation.**
24 |
25 | The packaging is kicked off automagically for you when a Github Release for Element Desktop is published.
26 |
27 | ### More detail on the github actions
28 |
29 | We moved to Github Actions for the following reasons:
30 |
31 | 1. Removing single point of failure
32 | 2. Improving reliability
33 | 3. Unblocking the packaging on a single individual
34 | 4. Improving parallelism
35 |
36 | The Windows builds are signed by SSL.com using their Cloud Key Adapter for eSigner.
37 | This allows us to use Microsoft's signtool to interface with eSigner and send them a hash of the exe along with
38 | credentials in exchange for a signed certificate which we attach onto all the relevant files.
39 |
40 | The Apple builds are signed using standard code signing means and then notarised to appease GateKeeper.
41 |
42 | The Linux builds are distributed via a signed reprepro repository.
43 |
44 | The packages.element.io site is a public Cloudflare R2 bucket which is deployed to solely from Github Actions.
45 | The main bucket in R2 is `packages-element-io` which is a direct mapping of packages.element.io,
46 | we have a workflow which generates the index.html files there to imitate a public index which Cloudflare does not currently support.
47 | The reprepro database lives in `packages-element-io-db`.
48 | There is an additional pair of buckets of same name but appended with `-test` which can be used for testing,
49 | these land on https://packages-element-io-test.element.io/.
50 |
51 | ### Debian/Ubuntu Distributions
52 |
53 | We used to add a new distribution to match each Debian and Ubuntu release. As of April 2020, we have created a `default` distribution that everyone can use (since the packages have never differed by distribution anyway).
54 |
55 | The distribution configuration lives in https://github.com/vector-im/packages.element.io/blob/master/debian/conf/distributions as a canonical source.
56 |
--------------------------------------------------------------------------------
/docs/updates.md:
--------------------------------------------------------------------------------
1 | The Desktop app is capable of self-updating on macOS and Windows.
2 | The update server base url is configurable as `update_base_url` in config.json and can be served by a static file host,
3 | CDN or object storage.
4 |
5 | Currently all packaging & deployment is handled by [Github actions](https://github.com/vector-im/element-desktop/blob/develop/.github/workflows/build_and_deploy.yaml)
6 |
7 | # Windows
8 |
9 | On Windows the update mechanism used is [Squirrel.Windows](https://github.com/Squirrel/Squirrel.Windows)
10 | and can be served by any compatible Squirrel server, such as https://github.com/Tiliq/squirrel-server
11 |
12 | # macOS
13 |
14 | On macOS the update mechanism used is [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac)
15 | using the newer JSON format as documented [here](https://github.com/Squirrel/Squirrel.Mac#update-file-json-format).
16 |
--------------------------------------------------------------------------------
/docs/windows-requirements.md:
--------------------------------------------------------------------------------
1 | # Windows
2 |
3 | ## Requirements to build native modules
4 |
5 | We rely on Github Actions `windows-2022` plus a few extra utilities as per [the workflow](https://github.com/vector-im/element-desktop/blob/develop/.github/workflows/build_windows.yaml).
6 |
7 | If you want to build native modules, make sure that the following tools are installed on your system.
8 |
9 | - [Git for Windows](https://git-scm.com/download/win)
10 | - [Node 16](https://nodejs.org)
11 | - [Python 3](https://www.python.org/downloads/) (if you type 'python' into command prompt it will offer to install it from the windows store)
12 | - [Strawberry Perl](https://strawberryperl.com/)
13 | - [Rustup](https://rustup.rs/)
14 | - [NASM](https://www.nasm.us/)
15 |
16 | You can install the above tools using [Chocolatey](https://chocolatey.org/install):
17 |
18 | ```cmd
19 | choco install --no-progress -y git nodejs-lts yarn python StrawberryPerl rustup.install nasm magicsplat-tcl-tk
20 | ```
21 |
22 | - [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) with the following configuration:
23 | - On the Workloads tab:
24 | - Desktop & Mobile -> C++ build tools
25 | - On the Individual components tab:
26 | - MSVC VS 2019 C++ build tools
27 | - Windows 10 SDK (latest version available)
28 | - C++ CMake tools for Windows
29 |
30 | Once installed make sure all those utilities are accessible in your `PATH`.
31 |
32 | If you want to be able to build x86 targets from an x64 host install the right toolchain:
33 |
34 | ```cmd
35 | rustup toolchain install stable-i686-pc-windows-msvc
36 | rustup target add i686-pc-windows-msvc
37 | ```
38 |
39 | In order to load all the C++ utilities installed by Visual Studio you can run the following in a terminal window.
40 |
41 | ```
42 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64
43 | ```
44 |
45 | You can replace `amd64` with `x86` depending on your CPU architecture.
46 |
--------------------------------------------------------------------------------
/element.io/New_Vector_Ltd.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIF8DCCBNigAwIBAgIRAIZSd8hNVs3w8AhJIsQSoYYwDQYJKoZIhvcNAQELBQAw
3 | gZExCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
4 | BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTcwNQYD
5 | VQQDEy5DT01PRE8gUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gQ29kZSBTaWduaW5n
6 | IENBMB4XDTIwMDczMDAwMDAwMFoXDTIzMDczMDIzNTk1OVowgfMxETAPBgNVBAUT
7 | CDEwODczNjYxMRMwEQYLKwYBBAGCNzwCAQMTAkdCMR0wGwYDVQQPExRQcml2YXRl
8 | IE9yZ2FuaXphdGlvbjELMAkGA1UEBhMCR0IxDzANBgNVBBEMBlc0IDFRVTEYMBYG
9 | A1UECAwPTG9uZG9uLCBDaXR5IG9mMQ8wDQYDVQQHDAZMb25kb24xJzAlBgNVBAkM
10 | HjE0IFR1cm5oYW0gR3JlZW4sIFRlcnJhY2UgTWV3czEbMBkGA1UECgwSTmV3IFZl
11 | Y3RvciBMaW1pdGVkMRswGQYDVQQDDBJOZXcgVmVjdG9yIExpbWl0ZWQwggEiMA0G
12 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKDnGul2M7oCCN+3veGf7yfakSfMjK
13 | Kqeylyo0Nj8dUleNQvsRo0OHxIWJlbHDYglxebT109MqgyASr0SoeiqvXOSSFACD
14 | MYFoyerRGMnXEuomTU0y7+FE3p/hcim8+C5gS+rHr3YaJJuzzXRztjjCBGoY4was
15 | h9V8kYiyMMK0xi2WftOCBa1yYS32CInHIZVmdhzoK4k4YzSYIp57BWvnIUlDyCYk
16 | slNfp0SFbDrOGa7kbmy8HRfWPLjNmW5PPIrsf8LlnVRBfmTeUIJtV31w/FuMjeir
17 | pzYjzooXmpIrj96ecxdc9thP6etCUazvpowjfewu7UNWRUhGPtYn8v8rAgMBAAGj
18 | ggHdMIIB2TAfBgNVHSMEGDAWgBTfj/MgDOnKpgTYW1g3Kj2rRtyDSTAdBgNVHQ4E
19 | FgQUB9su4pCQXE5ZWFhB1eo48992LEcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB
20 | /wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEQYJYIZIAYb4QgEBBAQDAgQQMEkG
21 | A1UdIARCMEAwNQYMKwYBBAGyMQECAQYBMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8v
22 | c2VjdGlnby5jb20vQ1BTMAcGBWeBDAEDMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6
23 | Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET1JTQUV4dGVuZGVkVmFsaWRhdGlvbkNv
24 | ZGVTaWduaW5nQ0EuY3JsMIGGBggrBgEFBQcBAQR6MHgwUAYIKwYBBQUHMAKGRGh0
25 | dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET1JTQUV4dGVuZGVkVmFsaWRhdGlv
26 | bkNvZGVTaWduaW5nQ0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
27 | ZG9jYS5jb20wJgYDVR0RBB8wHaAbBggrBgEFBQcIA6APMA0MC0dCLTEwODczNjYx
28 | MA0GCSqGSIb3DQEBCwUAA4IBAQBZ8/YtqW/+VUoV8knKpdhMR2uTn8AEyVmfmoZA
29 | Ly5kdCRoKvFm/z4VK4cqF7gsUDxRIgDuMKKbWTtr1FEXuaZkUkOjicNcdYxc0pDn
30 | nFKBWAv5pN5OmnC9cVqIG7PtvD+8bKVzDdQRjpGMy6PY4rN2PYGfQ7KGgddEDD6m
31 | oM51jI/OTvCeU0Tyl0bixKEmpUJvbeQM9Ul2Y1o5Enx1Q9uda8xATb0HCMKgJ+GC
32 | iFHL2DNC3j1xK4QoZEIYgbTscj9rK4OMEov3PT/e1FwQyB5V9xdJ5i1MDBDD9fAf
33 | OiVgf90SffT7TgWUbA4Z+PtQHq/qNma0+dZWyeq7zYn2IIX3
34 | -----END CERTIFICATE-----
35 |
--------------------------------------------------------------------------------
/element.io/README:
--------------------------------------------------------------------------------
1 | This directory contains the config file for the official element.io distribution
2 | of Element Desktop.
3 |
4 | You probably do not want to build with this config unless you're building the
5 | official element.io distribution, or you'll find your builds will replace
6 | themselves with the element.io build.
7 |
--------------------------------------------------------------------------------
/element.io/nightly/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "update_base_url": "https://packages.element.io/nightly/update/",
3 | "default_server_name": "matrix.org",
4 | "default_server_config": {
5 | "m.homeserver": {
6 | "base_url": "https://matrix-client.matrix.org"
7 | },
8 | "m.identity_server": {
9 | "base_url": "https://vector.im"
10 | }
11 | },
12 | "brand": "Element Nightly",
13 | "integrations_ui_url": "https://scalar.vector.im/",
14 | "integrations_rest_url": "https://scalar.vector.im/api",
15 | "integrations_widgets_urls": [
16 | "https://scalar.vector.im/_matrix/integrations/v1",
17 | "https://scalar.vector.im/api",
18 | "https://scalar-staging.vector.im/_matrix/integrations/v1",
19 | "https://scalar-staging.vector.im/api",
20 | "https://scalar-staging.riot.im/scalar/api"
21 | ],
22 | "bug_report_endpoint_url": "https://element.io/bugreports/submit",
23 | "uisi_autorageshake_app": "element-auto-uisi",
24 | "show_labs_settings": true,
25 | "room_directory": {
26 | "servers": ["matrix.org", "gitter.im"]
27 | },
28 | "enable_presence_by_hs_url": {
29 | "https://matrix.org": false,
30 | "https://matrix-client.matrix.org": false
31 | },
32 | "terms_and_conditions_links": [
33 | {
34 | "url": "https://element.io/privacy",
35 | "text": "Privacy Policy"
36 | },
37 | {
38 | "url": "https://element.io/cookie-policy",
39 | "text": "Cookie Policy"
40 | }
41 | ],
42 | "sentry": {
43 | "dsn": "https://029a0eb289f942508ae0fb17935bd8c5@sentry.matrix.org/6",
44 | "environment": "nightly"
45 | },
46 | "posthog": {
47 | "project_api_key": "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
48 | "api_host": "https://posthog.element.io"
49 | },
50 | "privacy_policy_url": "https://element.io/cookie-policy",
51 | "features": {
52 | "threadsActivityCentre": true,
53 | "feature_spotlight": true,
54 | "feature_group_calls": true,
55 | "feature_video_rooms": true,
56 | "feature_element_call_video_rooms": true
57 | },
58 | "setting_defaults": {
59 | "RustCrypto.staged_rollout_percent": 100
60 | },
61 | "element_call": {
62 | "url": "https://call.element.dev"
63 | },
64 | "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
65 | }
66 |
--------------------------------------------------------------------------------
/element.io/release/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "update_base_url": "https://packages.element.io/desktop/update/",
3 | "default_server_name": "matrix.org",
4 | "default_server_config": {
5 | "m.homeserver": {
6 | "base_url": "https://matrix-client.matrix.org"
7 | },
8 | "m.identity_server": {
9 | "base_url": "https://vector.im"
10 | }
11 | },
12 | "brand": "Element",
13 | "integrations_ui_url": "https://scalar.vector.im/",
14 | "integrations_rest_url": "https://scalar.vector.im/api",
15 | "integrations_widgets_urls": [
16 | "https://scalar.vector.im/_matrix/integrations/v1",
17 | "https://scalar.vector.im/api",
18 | "https://scalar-staging.vector.im/_matrix/integrations/v1",
19 | "https://scalar-staging.vector.im/api",
20 | "https://scalar-staging.riot.im/scalar/api"
21 | ],
22 | "bug_report_endpoint_url": "https://element.io/bugreports/submit",
23 | "uisi_autorageshake_app": "element-auto-uisi",
24 | "room_directory": {
25 | "servers": ["matrix.org", "gitter.im"]
26 | },
27 | "show_labs_settings": false,
28 | "enable_presence_by_hs_url": {
29 | "https://matrix.org": false,
30 | "https://matrix-client.matrix.org": false
31 | },
32 | "terms_and_conditions_links": [
33 | {
34 | "url": "https://element.io/privacy",
35 | "text": "Privacy Policy"
36 | },
37 | {
38 | "url": "https://element.io/cookie-policy",
39 | "text": "Cookie Policy"
40 | }
41 | ],
42 | "posthog": {
43 | "project_api_key": "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
44 | "api_host": "https://posthog.element.io"
45 | },
46 | "privacy_policy_url": "https://element.io/cookie-policy",
47 | "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
48 | "setting_defaults": {
49 | "RustCrypto.staged_rollout_percent": 60
50 | },
51 | "features": {
52 | "feature_video_rooms": true,
53 | "feature_group_calls": true,
54 | "feature_element_call_video_rooms": true
55 | },
56 | "element_call": {
57 | "url": "https://call.element.io"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/hak/matrix-seshat/build.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import type HakEnv from "../../scripts/hak/hakEnv.js";
10 | import type { DependencyInfo } from "../../scripts/hak/dep.js";
11 |
12 | export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
13 | const env = hakEnv.makeGypEnv();
14 |
15 | if (!hakEnv.isHost()) {
16 | env.CARGO_BUILD_TARGET = hakEnv.getTargetId();
17 | }
18 |
19 | console.log("Running yarn install");
20 | await hakEnv.spawn("yarn", ["install"], {
21 | cwd: moduleInfo.moduleBuildDir,
22 | env,
23 | shell: true,
24 | });
25 |
26 | const buildTarget = hakEnv.wantsStaticSqlCipher() ? "build-bundled" : "build";
27 |
28 | console.log("Running yarn build");
29 | await hakEnv.spawn("yarn", ["run", buildTarget], {
30 | cwd: moduleInfo.moduleBuildDir,
31 | env,
32 | shell: true,
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/hak/matrix-seshat/check.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import childProcess from "node:child_process";
10 | import fsProm from "node:fs/promises";
11 |
12 | import type HakEnv from "../../scripts/hak/hakEnv.js";
13 | import type { Tool } from "../../scripts/hak/hakEnv.js";
14 | import type { DependencyInfo } from "../../scripts/hak/dep.js";
15 |
16 | export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
17 | const tools: Tool[] = [
18 | ["rustc", "--version"],
19 | ["python", "--version"], // node-gyp uses python for reasons beyond comprehension
20 | ];
21 | if (hakEnv.isWin()) {
22 | tools.push(["perl", "--version"]); // for openssl configure
23 | tools.push(["nasm", "-v"]); // for openssl building
24 | tools.push(["patch", "--version"]); // to patch sqlcipher Makefile.msc
25 | tools.push(["nmake", "/?"]);
26 | } else {
27 | tools.push(["make", "--version"]);
28 | }
29 | await hakEnv.checkTools(tools);
30 |
31 | // Ensure Rust target exists (nb. we avoid depending on rustup)
32 | await new Promise((resolve, reject) => {
33 | const rustc = childProcess.execFile(
34 | "rustc",
35 | ["--target", hakEnv.getTargetId(), "--emit=obj", "-o", "tmp", "-"],
36 | (err, out) => {
37 | if (err) {
38 | reject(
39 | "rustc can't build for target " +
40 | hakEnv.getTargetId() +
41 | ": ensure target is installed via `rustup target add " +
42 | hakEnv.getTargetId() +
43 | "` " +
44 | "or your package manager if not using `rustup`",
45 | );
46 | }
47 | fsProm.unlink("tmp").then(resolve);
48 | },
49 | );
50 | rustc.stdin!.write("fn main() {}");
51 | rustc.stdout!.pipe(process.stdout);
52 | rustc.stderr!.pipe(process.stderr);
53 | rustc.stdin!.end();
54 | });
55 | }
56 |
--------------------------------------------------------------------------------
/hak/matrix-seshat/hak.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "check": "check.ts",
4 | "build": "build.ts"
5 | },
6 | "copy": "index.node"
7 | }
8 |
--------------------------------------------------------------------------------
/hak/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "esModuleInterop": true,
5 | "target": "es2022",
6 | "sourceMap": false,
7 | "strict": true,
8 | "lib": ["es2022"]
9 | },
10 | "include": ["../scripts/@types/*.d.ts", "./**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/knip.ts:
--------------------------------------------------------------------------------
1 | import { KnipConfig } from "knip";
2 |
3 | export default {
4 | entry: ["src/electron-main.ts", "src/preload.ts", "electron-builder.ts", ".eslintrc-*.js", "scripts/**", "hak/**"],
5 | project: ["**/*.{js,ts}"],
6 | ignoreDependencies: [
7 | // Brought in via hak scripts
8 | "matrix-seshat",
9 | // Required for `action-validator`
10 | "@action-validator/*",
11 | // Used for git pre-commit hooks
12 | "husky",
13 | ],
14 | ignoreBinaries: ["jq", "scripts/in-docker.sh"],
15 | } satisfies KnipConfig;
16 |
--------------------------------------------------------------------------------
/localazy.json:
--------------------------------------------------------------------------------
1 | {
2 | "readKey": "a7688614897667993891-866e2615b0a22e6ccef56aea9b10e815efa3e1296752a7a30bd9925f1a8f33e7",
3 |
4 | "upload": {
5 | "type": "json",
6 | "keySeparator": "|",
7 | "deprecate": "file",
8 | "features": ["plural_object", "filter_untranslated"],
9 | "files": [
10 | {
11 | "pattern": "src/i18n/strings/en_EN.json",
12 | "file": "element-desktop.json",
13 | "lang": "inherited"
14 | },
15 | {
16 | "group": "existing",
17 | "pattern": "src/i18n/strings/*.json",
18 | "file": "element-desktop.json",
19 | "excludes": ["src/i18n/strings/en_EN.json"],
20 | "lang": "${autodetectLang}"
21 | }
22 | ]
23 | },
24 |
25 | "download": {
26 | "files": [
27 | {
28 | "conditions": "equals: ${file}, element-desktop.json",
29 | "output": "src/i18n/strings/${langLsrUnderscore}.json"
30 | }
31 | ],
32 | "includeSourceLang": "${includeSourceLang|false}",
33 | "langAliases": {
34 | "en": "en-EN"
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2023 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import { defineConfig } from "@playwright/test";
10 |
11 | const projects = [
12 | "macos",
13 | "win-x64",
14 | "win-ia32",
15 | "win-arm64",
16 | "linux-amd64-sqlcipher-system",
17 | "linux-amd64-sqlcipher-static",
18 | "linux-arm64-sqlcipher-system",
19 | "linux-arm64-sqlcipher-static",
20 | ];
21 |
22 | export default defineConfig({
23 | // Allows the GitHub action to specify a project name (OS + arch) for the combined report to make sense
24 | // workaround for https://github.com/microsoft/playwright/issues/33521
25 | projects: process.env.CI
26 | ? projects.map((name) => ({
27 | name,
28 | }))
29 | : undefined,
30 | use: {
31 | viewport: { width: 1280, height: 720 },
32 | video: "retain-on-failure",
33 | trace: "on-first-retry",
34 | },
35 | testDir: "playwright/e2e",
36 | outputDir: "playwright/test-results",
37 | workers: 1,
38 | retries: process.env.CI ? 2 : 0,
39 | reporter: process.env.CI ? [["blob"], ["github"]] : [["html", { outputFolder: "playwright/html-report" }]],
40 | snapshotDir: "playwright/snapshots",
41 | snapshotPathTemplate: "{snapshotDir}/{testFilePath}/{arg}-{platform}{ext}",
42 | timeout: 30 * 1000,
43 | });
44 |
--------------------------------------------------------------------------------
/playwright/.gitignore:
--------------------------------------------------------------------------------
1 | /test-results/
2 | /html-report/
3 | # Only commit snapshots from Linux
4 | /snapshots/**/*.png
5 | !/snapshots/**/*-linux.png
6 |
--------------------------------------------------------------------------------
/playwright/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/playwright:v1.52.0-jammy@sha256:ff2946177f0756c87482c0ef958b7cfbf389b92525ace78a1c9890281d0d60f4
2 |
3 | WORKDIR /work/element-desktop
4 |
5 | RUN apt-get update && apt-get -y install xvfb && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/*
6 |
7 | USER 1000:1000
8 |
9 | COPY docker-entrypoint.sh /opt/docker-entrypoint.sh
10 | ENTRYPOINT ["bash", "/opt/docker-entrypoint.sh"]
11 |
--------------------------------------------------------------------------------
/playwright/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | echo "Starting Xvfb"
6 | Xvfb :99 -ac &
7 | sleep 2
8 |
9 | export DISPLAY=:99
10 |
11 | npx playwright test --update-snapshots --reporter line $1
12 |
--------------------------------------------------------------------------------
/playwright/e2e/launch/config-options.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 |
4 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5 | Please see LICENSE files in the repository root for full details.
6 | */
7 |
8 | import { resolve, dirname } from "node:path";
9 | import { fileURLToPath } from "node:url";
10 |
11 | import { test, expect } from "../../element-desktop-test.js";
12 |
13 | const __dirname = dirname(fileURLToPath(import.meta.url));
14 |
15 | test.describe("App config options", () => {
16 | test.describe("Should load custom config via env", () => {
17 | test.slow();
18 | test.use({
19 | extraEnv: {
20 | ELEMENT_DESKTOP_CONFIG_JSON: resolve(__dirname, "../..", "fixtures/custom-config.json"),
21 | },
22 | });
23 | test("should launch and use configured homeserver", async ({ page }) => {
24 | await page.locator("#matrixchat").waitFor();
25 | await page.locator(".mx_Welcome").waitFor();
26 | await expect(page).toHaveURL("vector://vector/webapp/#/welcome");
27 | await page.getByText("Sign in").click();
28 | await page.getByText("matrix.example.org", { exact: true }).waitFor();
29 | });
30 | });
31 | test.describe("Should load custom config via argument", () => {
32 | test.slow();
33 | test.use({
34 | extraArgs: ["--config", resolve(__dirname, "../..", "fixtures/custom-config.json")],
35 | });
36 | test("should launch and use configured homeserver", async ({ page }) => {
37 | await page.locator("#matrixchat").waitFor();
38 | await page.locator(".mx_Welcome").waitFor();
39 | await expect(page).toHaveURL("vector://vector/webapp/#/welcome");
40 | await page.getByText("Sign in").click();
41 | await page.getByText("matrix.example.org", { exact: true }).waitFor();
42 | });
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/playwright/e2e/launch/launch.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import keytar from "keytar-forked";
10 |
11 | import { test, expect } from "../../element-desktop-test.js";
12 |
13 | declare global {
14 | interface ElectronPlatform {
15 | getEventIndexingManager():
16 | | {
17 | supportsEventIndexing(): Promise;
18 | }
19 | | undefined;
20 | getPickleKey(userId: string, deviceId: string): Promise;
21 | createPickleKey(userId: string, deviceId: string): Promise;
22 | }
23 |
24 | interface Window {
25 | mxPlatformPeg: {
26 | get(): ElectronPlatform;
27 | };
28 | }
29 | }
30 |
31 | test.describe("App launch", () => {
32 | test.slow();
33 |
34 | test.beforeEach(async ({ page }) => {
35 | await page.locator("#matrixchat").waitFor();
36 | await page.locator(".mx_Welcome").waitFor();
37 | });
38 |
39 | test("should launch and render the welcome view successfully", async ({ page }) => {
40 | await expect(page).toHaveURL("vector://vector/webapp/#/welcome");
41 | await expect(page).toHaveScreenshot();
42 | });
43 |
44 | test("should launch and render the welcome view successfully and support seshat", async ({ page }) => {
45 | await expect(
46 | page.evaluate(async () => {
47 | return window.mxPlatformPeg.get().getEventIndexingManager()?.supportsEventIndexing();
48 | }),
49 | ).resolves.toBeTruthy();
50 | });
51 |
52 | test.describe("safeStorage", () => {
53 | const userId = "@user:server";
54 | const deviceId = "ABCDEF";
55 |
56 | test("should be supported", async ({ page }) => {
57 | await expect(
58 | page.evaluate(
59 | ([userId, deviceId]) => window.mxPlatformPeg.get().createPickleKey(userId, deviceId),
60 | [userId, deviceId],
61 | ),
62 | ).resolves.not.toBeNull();
63 | });
64 |
65 | test.describe("migrate from keytar", () => {
66 | test.skip(
67 | process.env.GITHUB_ACTIONS && ["linux", "darwin"].includes(process.platform),
68 | "GitHub Actions hosted runner are not a compatible environment for this test",
69 | );
70 |
71 | const pickleKey = "DEADBEEF1234";
72 | const keytarService = "element.io";
73 | const keytarKey = `${userId}|${deviceId}`;
74 |
75 | test.beforeAll(async () => {
76 | await keytar.setPassword(keytarService, keytarKey, pickleKey);
77 | await expect(keytar.getPassword(keytarService, keytarKey)).resolves.toBe(pickleKey);
78 | });
79 | test.afterAll(async () => {
80 | await keytar.deletePassword(keytarService, keytarKey);
81 | });
82 |
83 | test("should migrate successfully", async ({ page }) => {
84 | await expect(
85 | page.evaluate(
86 | ([userId, deviceId]) => window.mxPlatformPeg.get().getPickleKey(userId, deviceId),
87 | [userId, deviceId],
88 | ),
89 | ).resolves.toBe(pickleKey);
90 | });
91 | });
92 | });
93 |
94 | test.describe("--no-update", () => {
95 | test.use({
96 | extraArgs: ["--no-update"],
97 | });
98 |
99 | // XXX: this test works fine locally but in CI the app start races with the test plumbing up the stdout/stderr pipes
100 | // which means the logs are missed, disabling for now.
101 | test.skip("should respect option", async ({ page, stdout }) => {
102 | expect(stdout.data.toString()).toContain("Auto update disabled via command line flag");
103 | });
104 | });
105 | });
106 |
--------------------------------------------------------------------------------
/playwright/e2e/launch/oidc.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2025 New Vector Ltd.
3 |
4 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5 | Please see LICENSE files in the repository root for full details.
6 | */
7 |
8 | import { test, expect } from "../../element-desktop-test.js";
9 |
10 | declare global {
11 | interface ElectronPlatform {
12 | getOidcCallbackUrl(): URL;
13 | }
14 |
15 | interface Window {
16 | mxPlatformPeg: {
17 | get(): ElectronPlatform;
18 | };
19 | }
20 | }
21 |
22 | test.describe("OIDC Native", () => {
23 | test.slow();
24 |
25 | test.beforeEach(async ({ page }) => {
26 | await page.locator(".mx_Welcome").waitFor();
27 | });
28 |
29 | test("should use OIDC callback URL without authority component", async ({ page }) => {
30 | await expect(
31 | page.evaluate(() => {
32 | return window.mxPlatformPeg.get().getOidcCallbackUrl().toString();
33 | }),
34 | ).resolves.toMatch(/io\.element\.(desktop|nightly):\/vector\/webapp\//);
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/playwright/element-desktop-test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2023 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import { _electron as electron, test as base, expect as baseExpect, type ElectronApplication } from "@playwright/test";
10 | import fs from "node:fs/promises";
11 | import path, { dirname } from "node:path";
12 | import os from "node:os";
13 | import { fileURLToPath } from "node:url";
14 | import { PassThrough } from "node:stream";
15 |
16 | /**
17 | * A PassThrough stream that captures all data written to it.
18 | */
19 | class CapturedPassThrough extends PassThrough {
20 | private _chunks = [];
21 |
22 | public constructor() {
23 | super();
24 | super.on("data", this.onData);
25 | }
26 |
27 | private onData = (chunk): void => {
28 | this._chunks.push(chunk);
29 | };
30 |
31 | public get data(): Buffer {
32 | return Buffer.concat(this._chunks);
33 | }
34 | }
35 |
36 | interface Fixtures {
37 | app: ElectronApplication;
38 | tmpDir: string;
39 | extraEnv: Record;
40 | extraArgs: string[];
41 |
42 | // Utilities to capture stdout and stderr for tests to make assertions against
43 | stdout: CapturedPassThrough;
44 | stderr: CapturedPassThrough;
45 | }
46 |
47 | const __dirname = dirname(fileURLToPath(import.meta.url));
48 |
49 | export const test = base.extend({
50 | extraEnv: {},
51 | extraArgs: [],
52 |
53 | // eslint-disable-next-line no-empty-pattern
54 | stdout: async ({}, use) => {
55 | await use(new CapturedPassThrough());
56 | },
57 | // eslint-disable-next-line no-empty-pattern
58 | stderr: async ({}, use) => {
59 | await use(new CapturedPassThrough());
60 | },
61 |
62 | // eslint-disable-next-line no-empty-pattern
63 | tmpDir: async ({}, use) => {
64 | const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "element-desktop-tests-"));
65 | await use(tmpDir);
66 | await fs.rm(tmpDir, { recursive: true });
67 | },
68 | app: async ({ tmpDir, extraEnv, extraArgs, stdout, stderr }, use) => {
69 | const args = ["--profile-dir", tmpDir, ...extraArgs];
70 |
71 | if (process.env.GITHUB_ACTIONS) {
72 | if (process.platform === "linux") {
73 | // GitHub Actions hosted runner lacks dbus and a compatible keyring, so we need to force plaintext storage
74 | args.push("--storage-mode", "force-plaintext");
75 | } else if (process.platform === "darwin") {
76 | // GitHub Actions hosted runner has no working default keychain, so allow plaintext storage
77 | args.push("--storage-mode", "allow-plaintext");
78 | }
79 | }
80 |
81 | const executablePath = process.env["ELEMENT_DESKTOP_EXECUTABLE"];
82 | if (!executablePath) {
83 | // Unpackaged mode testing
84 | args.unshift(path.join(__dirname, "..", "lib", "electron-main.js"));
85 | }
86 |
87 | console.log(`Launching '${executablePath}' with args ${args.join(" ")}`);
88 |
89 | const app = await electron.launch({
90 | env: {
91 | ...process.env,
92 | ...extraEnv,
93 | },
94 | executablePath,
95 | args,
96 | });
97 |
98 | app.process().stdout.pipe(stdout).pipe(process.stdout);
99 | app.process().stderr.pipe(stderr).pipe(process.stderr);
100 |
101 | await app.firstWindow();
102 |
103 | // Block matrix.org access to ensure consistent tests
104 | const context = app.context();
105 | await context.route("https://matrix.org/**", async (route) => {
106 | await route.abort();
107 | });
108 |
109 | await use(app);
110 | },
111 | page: async ({ app }, use) => {
112 | const window = await app.firstWindow();
113 | await use(window);
114 | await app.close().catch((e) => {
115 | console.error(e);
116 | });
117 | },
118 | });
119 |
120 | export const expect = baseExpect;
121 |
--------------------------------------------------------------------------------
/playwright/fixtures/custom-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "default_server_config": {
3 | "m.homeserver": {
4 | "base_url": "https://matrix.example.org"
5 | },
6 | "m.identity_server": {
7 | "base_url": "https://identity.example.org"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/playwright/snapshots/launch/launch.spec.ts/App-launch-should-launch-and-render-the-welcome-view-successfully-1-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/playwright/snapshots/launch/launch.spec.ts/App-launch-should-launch-and-render-the-welcome-view-successfully-1-linux.png
--------------------------------------------------------------------------------
/playwright/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "resolveJsonModule": true,
4 | "moduleResolution": "node",
5 | "esModuleInterop": true,
6 | "target": "es2022",
7 | "module": "es2022",
8 | "lib": ["es2022", "dom"]
9 | },
10 | "include": ["**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/res/img/element.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/res/img/element.ico
--------------------------------------------------------------------------------
/res/img/element.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/element-hq/element-desktop/58ef3d277f3a0d385d9e4061b69e8ae07f54acdc/res/img/element.png
--------------------------------------------------------------------------------
/scripts/branch-match.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script for downloading a branch of element-web matching the branch a PR is contributed from
4 |
5 | set -x
6 |
7 | deforg="element-hq"
8 | defrepo="element-web"
9 |
10 | # The PR_NUMBER variable must be set explicitly.
11 | default_org_repo=${GITHUB_REPOSITORY:-"$deforg/$defrepo"}
12 | PR_ORG=${PR_ORG:-${default_org_repo%%/*}}
13 | PR_REPO=${PR_REPO:-${default_org_repo##*/}}
14 |
15 | # A function that clones a branch of a repo based on the org, repo and branch
16 | clone() {
17 | org=$1
18 | repo=$2
19 | branch=$3
20 | if [ -n "$branch" ]
21 | then
22 | echo "Trying to use $org/$repo#$branch"
23 | # Disable auth prompts: https://serverfault.com/a/665959
24 | GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
25 | fi
26 | }
27 |
28 | echo "Getting info about a PR with number $PR_NUMBER"
29 | apiEndpoint="https://api.github.com/repos/$PR_ORG/$PR_REPO/pulls/$PR_NUMBER"
30 | head=$(curl "$apiEndpoint" | jq -r '.head.label')
31 |
32 | # for forks, $head will be in the format "fork:branch", so we split it by ":"
33 | # into an array. On non-forks, this has the effect of splitting into a single
34 | # element array given ":" shouldn't appear in the head - it'll just be the
35 | # branch name. Based on the results, we clone.
36 | BRANCH_ARRAY=(${head//:/ })
37 | TRY_ORG=$deforg
38 | TRY_BRANCH=${BRANCH_ARRAY[0]}
39 | if [[ "$head" == *":"* ]]; then
40 | # ... but only match that fork if it's a real fork
41 | if [ "${BRANCH_ARRAY[0]}" != "$PR_ORG" ]; then
42 | TRY_ORG=${BRANCH_ARRAY[0]}
43 | fi
44 | TRY_BRANCH=${BRANCH_ARRAY[1]}
45 | fi
46 | clone "$TRY_ORG" "$defrepo" "$TRY_BRANCH"
47 |
48 | exit 1
49 |
--------------------------------------------------------------------------------
/scripts/cl.bat:
--------------------------------------------------------------------------------
1 | REM Batch file to aid in cross-compiling sqlcipher for Windows ARM64
2 | REM Full path should be passed to Makefile.msc as NCC env var
3 |
4 | setlocal
5 | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" %VSCMD_ARG_HOST_ARCH%
6 | cl.exe %*
7 | endlocal
8 |
--------------------------------------------------------------------------------
/scripts/copy-res.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S npx tsx
2 |
3 | // copies resources into the lib directory.
4 |
5 | import parseArgs from "minimist";
6 | import * as chokidar from "chokidar";
7 | import * as path from "node:path";
8 | import * as fs from "node:fs";
9 |
10 | const argv = parseArgs(process.argv.slice(2), {});
11 |
12 | const watch = argv.w;
13 | const verbose = argv.v;
14 |
15 | function errCheck(err: unknown): void {
16 | if (err) {
17 | console.error(err instanceof Error ? err.message : err);
18 | process.exit(1);
19 | }
20 | }
21 |
22 | const I18N_BASE_PATH = "src/i18n/strings/";
23 | const INCLUDE_LANGS = fs.readdirSync(I18N_BASE_PATH).filter((fn) => fn.endsWith(".json"));
24 |
25 | // Ensure lib, lib/i18n and lib/i18n/strings all exist
26 | fs.mkdirSync("lib/i18n/strings", { recursive: true });
27 |
28 | type Translations = Record | string>;
29 |
30 | function genLangFile(file: string, dest: string): void {
31 | const translations: Translations = {};
32 | [file].forEach(function (f) {
33 | if (fs.existsSync(f)) {
34 | try {
35 | Object.assign(translations, JSON.parse(fs.readFileSync(f).toString()));
36 | } catch (e) {
37 | console.error("Failed: " + f, e);
38 | throw e;
39 | }
40 | }
41 | });
42 |
43 | const json = JSON.stringify(translations, null, 4);
44 | const filename = path.basename(file);
45 |
46 | fs.writeFileSync(dest + filename, json);
47 | if (verbose) {
48 | console.log("Generated language file: " + filename);
49 | }
50 | }
51 |
52 | /*
53 | watch the input files for a given language,
54 | regenerate the file, and regenerating languages.json with the new filename
55 | */
56 | function watchLanguage(file: string, dest: string): void {
57 | // XXX: Use a debounce because for some reason if we read the language
58 | // file immediately after the FS event is received, the file contents
59 | // appears empty. Possibly https://github.com/nodejs/node/issues/6112
60 | let makeLangDebouncer: NodeJS.Timeout | undefined;
61 | const makeLang = (): void => {
62 | if (makeLangDebouncer) {
63 | clearTimeout(makeLangDebouncer);
64 | }
65 | makeLangDebouncer = setTimeout(() => {
66 | genLangFile(file, dest);
67 | }, 500);
68 | };
69 |
70 | chokidar.watch(file).on("add", makeLang).on("change", makeLang).on("error", errCheck);
71 | }
72 |
73 | // language resources
74 | const I18N_DEST = "lib/i18n/strings/";
75 | INCLUDE_LANGS.forEach((file): void => {
76 | genLangFile(I18N_BASE_PATH + file, I18N_DEST);
77 | }, {});
78 |
79 | if (watch) {
80 | INCLUDE_LANGS.forEach((file) => watchLanguage(I18N_BASE_PATH + file, I18N_DEST));
81 | }
82 |
--------------------------------------------------------------------------------
/scripts/generate-nightly-version.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S npx tsx
2 |
3 | /**
4 | * Script to generate incremental Nightly build versions, based on the latest Nightly build version of that kind.
5 | * The version format is YYYYMMDDNN where NN is in case we need to do multiple versions in a day.
6 | *
7 | * NB. on windows, squirrel will try to parse the version number parts, including this string, into 32-bit integers,
8 | * which is fine as long as we only add two digits to the end...
9 | */
10 |
11 | import parseArgs from "minimist";
12 |
13 | const argv = parseArgs<{
14 | latest?: string;
15 | }>(process.argv.slice(2), {
16 | string: ["latest"],
17 | });
18 |
19 | function parseVersion(version: string): [Date, number] {
20 | const year = parseInt(version.slice(0, 4), 10);
21 | const month = parseInt(version.slice(4, 6), 10);
22 | const day = parseInt(version.slice(6, 8), 10);
23 | const num = parseInt(version.slice(8, 10), 10);
24 | return [new Date(year, month - 1, day), num];
25 | }
26 |
27 | const [latestDate, latestNum] = argv.latest ? parseVersion(argv.latest) : [];
28 |
29 | const now = new Date();
30 | const month = (now.getMonth() + 1).toString().padStart(2, "0");
31 | const date = now.getDate().toString().padStart(2, "0");
32 | let buildNum = 1;
33 | if (latestDate && new Date(latestDate).getDate().toString().padStart(2, "0") === date) {
34 | buildNum = latestNum! + 1;
35 | }
36 |
37 | if (buildNum > 99) {
38 | throw new Error("Maximum number of Nightlies exceeded on this day.");
39 | }
40 |
41 | console.log(now.getFullYear() + month + date + buildNum.toString().padStart(2, "0"));
42 |
--------------------------------------------------------------------------------
/scripts/get-version.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S npx tsx
2 |
3 | /*
4 | * Checks for the presence of a webapp, inspects its version and prints it
5 | */
6 |
7 | import url from "node:url";
8 |
9 | import { versionFromAsar } from "./set-version.js";
10 |
11 | async function main(): Promise {
12 | const version = await versionFromAsar();
13 | console.log(version);
14 |
15 | return 0;
16 | }
17 |
18 | if (import.meta.url.startsWith("file:")) {
19 | const modulePath = url.fileURLToPath(import.meta.url);
20 | if (process.argv[1] === modulePath) {
21 | main()
22 | .then((ret) => {
23 | process.exit(ret);
24 | })
25 | .catch((e) => {
26 | console.error(e);
27 | process.exit(1);
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/scripts/glibc-check.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Source https://gist.github.com/vladimyr/9a03481154cd3048a486bdf71e5e1535/57e57a6ace6fb2c8bba948bce726df7a96c3f99f
4 | # This scripts lets you check which minimum GLIBC version an executable requires.
5 | # Simply run './glibc-check.sh path/to/your/binary'
6 | MAX_GLIBC="${MAX_GLIBC:-2.28}"
7 |
8 | BINARY="$1"
9 |
10 | # Version comparison function in bash
11 | vercomp() {
12 | if [[ $1 == "$2" ]]; then
13 | return 0
14 | fi
15 | local i ver1 ver2
16 | IFS="." read -ra ver1 <<<"$1"
17 | IFS="." read -ra ver2 <<<"$2"
18 | # fill empty fields in ver1 with zeros
19 | for ((i = ${#ver1[@]}; i < ${#ver2[@]}; i++)); do
20 | ver1[i]=0
21 | done
22 | for ((i = 0; i < ${#ver1[@]}; i++)); do
23 | if [[ -z ${ver2[i]} ]]; then
24 | # fill empty fields in ver2 with zeros
25 | ver2[i]=0
26 | fi
27 | if ((10#${ver1[i]} > 10#${ver2[i]})); then
28 | return 1
29 | fi
30 | if ((10#${ver1[i]} < 10#${ver2[i]})); then
31 | return 2
32 | fi
33 | done
34 | return 0
35 | }
36 |
37 | IFS="
38 | "
39 | VERS=$(objdump -T "$BINARY" | grep GLIBC_ | sed 's/.*GLIBC_\([.0-9]*\).*/\1/g' | sort -u)
40 |
41 | for VER in $VERS; do
42 | vercomp "$VER" "$MAX_GLIBC"
43 | COMP=$?
44 | if [[ $COMP -eq 1 ]]; then
45 | echo "Error! ${BINARY} requests GLIBC ${VER}, which is higher than target ${MAX_GLIBC}"
46 | echo "Affected symbols:"
47 | objdump -T "$BINARY" | grep -F "GLIBC_${VER}"
48 | echo "Looking for symbols in libraries..."
49 | for LIBRARY in $(ldd "$BINARY" | cut -d ' ' -f 3); do
50 | echo "$LIBRARY"
51 | objdump -T "$LIBRARY" | grep -F "GLIBC_${VER}"
52 | done
53 | exit 27
54 | else
55 | echo "Found version ${VER}"
56 | fi
57 | done
58 |
--------------------------------------------------------------------------------
/scripts/hak/README.md:
--------------------------------------------------------------------------------
1 | # hak
2 |
3 | This tool builds native dependencies for element-desktop. Here follows some very minimal
4 | documentation for it.
5 |
6 | Goals:
7 |
8 | - Must build compiled native node modules in a shippable state
9 | (ie. only dynamically linked against libraries that will be on the
10 | target system, all unnecessary files removed).
11 | - Must be able to build any native module, no matter what build system
12 | it uses (electron-rebuild is supposed to do this job but only works
13 | for modules that use gyp).
14 |
15 | It's also loosely designed to be a general tool and agnostic to what it's
16 | actually building. It's used here to build modules for the electron app
17 | but should work equally well for building modules for normal node.
18 |
19 | # Running
20 |
21 | Hak is invoked with a command and a dependency, eg. `yarn run hak fetch matrix-seshat`.
22 | If no dependencies are given, hak runs the command on all dependencies.
23 |
24 | # Files
25 |
26 | There are a lot of files involved:
27 |
28 | - scripts/hak/... - The tool itself
29 | - hak/[dependency] - Files provided by the app that tell hak how to build each of its native dependencies.
30 | Contains a hak.json file and also some script files, each of which must be referenced in hak.json.
31 | - .hak/ - Files generated by hak in the course of doing its job. Includes the dependency module itself and
32 | any of the native dependency's native dependencies.
33 | - .hak/[dependency]/build - An extracted copy of the dependency's node module used to build it.
34 | - .hak/[dependency]/out - Another extracted copy of the dependency, this one contains only what will be shipped.
35 |
36 | # Workings
37 |
38 | Hak works around native node modules that try to fetch or build their native component in
39 | the npm 'install' phase - modules that do this will typically end up with native components
40 | targeted to the build platform and the node that npm/yarn is using, which is no good for an
41 | electron app.
42 |
43 | It does this by installing it with `--ignore-scripts` and then using `yarn link` to keep the
44 | dependency module separate so yarn doesn't try to run its install / postinstall script
45 | at other points (eg. whenever you `yarn add` a random other dependency).
46 |
47 | This also means that the dependencies cannot be listed in `dependencies` or
48 | `devDependencies` in the project, since this would cause npm / yarn to install them and
49 | try to fetch their native parts. Instead, they are listed in `hakDependencies` which
50 | hak reads to install them for you.
51 |
52 | Hak will _not_ install dependencies for the copy of the module it links into your
53 | project, so if your native module has javascript dependencies that are actually needed at
54 | runtime (and not just to fetch / build the native parts), it won't work.
55 |
56 | Hak will generate a `.yarnrc` in the project directory to set the link directory to its
57 | own in the .hak directory (unless one already exists, in which case this is your problem).
58 |
59 | # Lifecycle
60 |
61 | Hak is divided into lifecycle stages, in order:
62 |
63 | - fetch - Download and extract the source of the dependency
64 | - link - Link the copy of the dependency into your node_modules directory
65 | - build - The Good Stuff. Configure and build any native dependencies, then the module itself.
66 | - copy - Copy the built artifact from the module build directory to the module output directory.
67 |
68 | # hak.json
69 |
70 | The scripts section contains scripts used for lifecycle stages that need them (fetch, build).
71 | It also contains 'prune' and 'copy' which are globs of files to delete from the output module directory
72 | and copy over from the module build directory to the output module directory, respectively.
73 |
74 | # Shortcomings
75 |
76 | Hak doesn't know about dependencies between lifecycle stages, ie. it doesn't know that you need to
77 | 'fetch' before you can 'build', etc. You get to run each individually, and remember
78 | the right order.
79 |
80 | There is also a _lot_ of duplication in the command execution: we should abstract away
81 | some of the boilerplate required to run commands & so forth.
82 |
--------------------------------------------------------------------------------
/scripts/hak/build.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import type { DependencyInfo } from "./dep.js";
10 | import type HakEnv from "./hakEnv.js";
11 |
12 | export default async function build(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
13 | await moduleInfo.scripts.build(hakEnv, moduleInfo);
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/hak/check.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import type { DependencyInfo } from "./dep.js";
10 | import type HakEnv from "./hakEnv.js";
11 |
12 | export default async function check(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
13 | await moduleInfo.scripts.check?.(hakEnv, moduleInfo);
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/hak/clean.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import path from "node:path";
10 | import { rimraf } from "rimraf";
11 |
12 | import type { DependencyInfo } from "./dep.js";
13 | import type HakEnv from "./hakEnv.js";
14 |
15 | export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
16 | await rimraf(moduleInfo.moduleDotHakDir);
17 | await rimraf(path.join(hakEnv.dotHakDir, "links", moduleInfo.name));
18 | await rimraf(path.join(hakEnv.projectRoot, "node_modules", moduleInfo.name));
19 | }
20 |
--------------------------------------------------------------------------------
/scripts/hak/copy.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import path from "node:path";
10 | import fsProm from "node:fs/promises";
11 | import childProcess from "node:child_process";
12 | import { glob } from "glob";
13 | import { mkdirp } from "mkdirp";
14 |
15 | import type HakEnv from "./hakEnv.js";
16 | import type { DependencyInfo } from "./dep.js";
17 |
18 | export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
19 | if (moduleInfo.cfg.copy) {
20 | // If there are multiple moduleBuildDirs, singular moduleBuildDir
21 | // is the same as moduleBuildDirs[0], so we're just listing the contents
22 | // of the first one.
23 | const files = await glob(moduleInfo.cfg.copy, {
24 | cwd: moduleInfo.moduleBuildDir,
25 | });
26 |
27 | if (moduleInfo.moduleBuildDirs.length > 1) {
28 | if (!hakEnv.isMac()) {
29 | console.error(
30 | "You asked me to copy multiple targets but I've only been taught " + "how to do that on macOS.",
31 | );
32 | throw new Error("Can't copy multiple targets on this platform");
33 | }
34 |
35 | for (const f of files) {
36 | const components = moduleInfo.moduleBuildDirs.map((dir) => path.join(dir, f));
37 | const dst = path.join(moduleInfo.moduleOutDir, f);
38 |
39 | await mkdirp(path.dirname(dst));
40 | await new Promise((resolve, reject) => {
41 | childProcess.execFile("lipo", ["-create", "-output", dst, ...components], (err) => {
42 | if (err) {
43 | reject(err);
44 | } else {
45 | resolve();
46 | }
47 | });
48 | });
49 | }
50 | } else {
51 | console.log("Copying files from " + moduleInfo.moduleBuildDir + " to " + moduleInfo.moduleOutDir);
52 | for (const f of files) {
53 | console.log("\t" + f);
54 | const src = path.join(moduleInfo.moduleBuildDir, f);
55 | const dst = path.join(moduleInfo.moduleOutDir, f);
56 |
57 | await mkdirp(path.dirname(dst));
58 | await fsProm.copyFile(src, dst);
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/scripts/hak/dep.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2021 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import type HakEnv from "./hakEnv.js";
10 |
11 | export interface DependencyInfo {
12 | name: string;
13 | version: string;
14 | cfg: Record;
15 | moduleHakDir: string;
16 | moduleDotHakDir: string;
17 | moduleTargetDotHakDir: string;
18 | moduleBuildDir: string;
19 | moduleBuildDirs: string[];
20 | moduleOutDir: string;
21 | nodeModuleBinDir: string;
22 | depPrefix: string;
23 | scripts: Record Promise>;
24 | }
25 |
--------------------------------------------------------------------------------
/scripts/hak/fetch.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import fsProm from "node:fs/promises";
10 | import pacote from "pacote";
11 |
12 | import type HakEnv from "./hakEnv.js";
13 | import type { DependencyInfo } from "./dep.js";
14 |
15 | export default async function fetch(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
16 | let haveModuleBuildDir;
17 | try {
18 | const stats = await fsProm.stat(moduleInfo.moduleBuildDir);
19 | haveModuleBuildDir = stats.isDirectory();
20 | } catch {
21 | haveModuleBuildDir = false;
22 | }
23 |
24 | if (haveModuleBuildDir) return;
25 |
26 | console.log("Fetching " + moduleInfo.name + "@" + moduleInfo.version);
27 |
28 | const packumentCache = new Map();
29 | await pacote.extract(`${moduleInfo.name}@${moduleInfo.version}`, moduleInfo.moduleBuildDir, {
30 | packumentCache,
31 | });
32 |
33 | console.log("Running yarn install in " + moduleInfo.moduleBuildDir);
34 | await hakEnv.spawn("yarn", ["install", "--ignore-scripts"], {
35 | cwd: moduleInfo.moduleBuildDir,
36 | });
37 |
38 | // also extract another copy to the output directory at this point
39 | // nb. we do not yarn install in the output copy: we could install in
40 | // production mode to get only runtime dependencies and not devDependencies,
41 | // but usually native modules come with dependencies that are needed for
42 | // building/fetching the native modules (eg. node-pre-gyp) rather than
43 | // actually used at runtime: we do not want to bundle these into our app.
44 | // We therefore just install no dependencies at all, and accept that any
45 | // actual runtime dependencies will have to be added to the main app's
46 | // dependencies. We can't tell what dependencies are real runtime deps
47 | // and which are just used for native module building.
48 | await pacote.extract(`${moduleInfo.name}@${moduleInfo.version}`, moduleInfo.moduleOutDir, {
49 | packumentCache,
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/scripts/hak/hakEnv.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import path from "node:path";
10 | import os from "node:os";
11 | import { getElectronVersionFromInstalled } from "app-builder-lib/out/electron/electronVersion.js";
12 | import childProcess, { type SpawnOptions } from "node:child_process";
13 |
14 | import { type Arch, type Target, TARGETS, getHost, isHostId, type TargetId } from "./target.js";
15 |
16 | async function getRuntimeVersion(projectRoot: string): Promise {
17 | const electronVersion = await getElectronVersionFromInstalled(projectRoot);
18 | if (!electronVersion) {
19 | throw new Error("Can't determine Electron version");
20 | }
21 | return electronVersion;
22 | }
23 |
24 | export type Tool = [cmd: string, ...args: string[]];
25 |
26 | export default class HakEnv {
27 | public readonly target: Target;
28 | public runtime: string = "electron";
29 | public runtimeVersion?: string;
30 | public dotHakDir: string;
31 |
32 | public constructor(
33 | public readonly projectRoot: string,
34 | targetId: TargetId | null,
35 | ) {
36 | const target = targetId ? TARGETS[targetId] : getHost();
37 |
38 | if (!target) {
39 | throw new Error(`Unknown target ${targetId}!`);
40 | }
41 | this.target = target;
42 | this.dotHakDir = path.join(this.projectRoot, ".hak");
43 | }
44 |
45 | public async init(): Promise {
46 | this.runtimeVersion = await getRuntimeVersion(this.projectRoot);
47 | }
48 |
49 | public getTargetId(): TargetId {
50 | return this.target.id;
51 | }
52 |
53 | public isWin(): boolean {
54 | return this.target.platform === "win32";
55 | }
56 |
57 | public isMac(): boolean {
58 | return this.target.platform === "darwin";
59 | }
60 |
61 | public isLinux(): boolean {
62 | return this.target.platform === "linux";
63 | }
64 |
65 | public isFreeBSD(): boolean {
66 | return this.target.platform === "freebsd";
67 | }
68 |
69 | public getTargetArch(): Arch {
70 | return this.target.arch;
71 | }
72 |
73 | public isHost(): boolean {
74 | return isHostId(this.target.id);
75 | }
76 |
77 | public makeGypEnv(): Record {
78 | return {
79 | ...process.env,
80 | npm_config_arch: this.target.arch,
81 | npm_config_target_arch: this.target.arch,
82 | npm_config_disturl: "https://electronjs.org/headers",
83 | npm_config_runtime: this.runtime,
84 | npm_config_target: this.runtimeVersion,
85 | npm_config_build_from_source: "true",
86 | npm_config_devdir: path.join(os.homedir(), ".electron-gyp"),
87 | };
88 | }
89 |
90 | public wantsStaticSqlCipher(): boolean {
91 | return !(this.isLinux() || this.isFreeBSD()) || process.env.SQLCIPHER_BUNDLED == "1";
92 | }
93 |
94 | public spawn(
95 | cmd: string,
96 | args: string[],
97 | { ignoreWinCmdlet, ...options }: SpawnOptions & { ignoreWinCmdlet?: boolean } = {},
98 | ): Promise {
99 | return new Promise((resolve, reject) => {
100 | const proc = childProcess.spawn(cmd + (!ignoreWinCmdlet && this.isWin() ? ".cmd" : ""), args, {
101 | stdio: "inherit",
102 | // We need shell mode on Windows to be able to launch `.cmd` executables
103 | // See https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
104 | shell: this.isWin(),
105 | ...options,
106 | });
107 | proc.on("exit", (code) => {
108 | if (code) {
109 | reject(code);
110 | } else {
111 | resolve();
112 | }
113 | });
114 | });
115 | }
116 |
117 | public async checkTools(tools: Tool[]): Promise {
118 | for (const [tool, ...args] of tools) {
119 | try {
120 | await this.spawn(tool, args, {
121 | ignoreWinCmdlet: true,
122 | stdio: ["ignore"],
123 | shell: false,
124 | });
125 | } catch {
126 | throw new Error(`Can't find ${tool}`);
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/scripts/hak/link.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2020 The Matrix.org Foundation C.I.C.
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import path from "node:path";
10 | import os from "node:os";
11 | import fsProm from "node:fs/promises";
12 |
13 | import type HakEnv from "./hakEnv.js";
14 | import { type DependencyInfo } from "./dep.js";
15 |
16 | export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise {
17 | const yarnrc = path.join(hakEnv.projectRoot, ".yarnrc");
18 | // this is fairly terrible but it's reasonably clunky to either parse a yarnrc
19 | // properly or get yarn to do it, so this will probably suffice for now.
20 | // We just check to see if there is a local .yarnrc at all, and assume that
21 | // if you've put one there yourself, you probably know what you're doing and
22 | // we won't meddle with it.
23 | // Also we do this for each module which is unnecessary, but meh.
24 | try {
25 | await fsProm.stat(yarnrc);
26 | } catch {
27 | await fsProm.writeFile(
28 | yarnrc,
29 | // XXX: 1. This must be absolute, as yarn will resolve link directories
30 | // relative to the closest project root, which means when we run it
31 | // in the dependency project, it will put the link directory in its
32 | // own project folder rather than the main project.
33 | // 2. The parser gets very confused by strings with colons in them
34 | // (ie. Windows absolute paths) but strings in quotes get parsed as
35 | // JSON so need to be valid JSON encoded strings (ie. have the
36 | // backslashes escaped). JSON.stringify will add quotes and escape.
37 | "--link-folder " + JSON.stringify(path.join(hakEnv.dotHakDir, "links")) + os.EOL,
38 | );
39 | }
40 |
41 | await hakEnv.spawn("yarn", ["link"], {
42 | cwd: moduleInfo.moduleOutDir,
43 | });
44 | await hakEnv.spawn("yarn", ["link", moduleInfo.name], {
45 | cwd: hakEnv.projectRoot,
46 | });
47 | }
48 |
--------------------------------------------------------------------------------
/scripts/in-docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | IMAGE=${DOCKER_IMAGE_NAME:-"element-desktop-dockerbuild"}
4 |
5 | docker inspect "$IMAGE" 2> /dev/null > /dev/null
6 | if [ $? != 0 ]; then
7 | echo "Docker image $IMAGE not found. Have you run yarn run docker:setup?"
8 | exit 1
9 | fi
10 |
11 | # Taken from https://www.electron.build/multi-platform-build#docker
12 | # Pass through any vars prefixed with INDOCKER_, removing the prefix
13 | docker run --rm -ti \
14 | --platform linux/amd64 \
15 | --env-file <(env | grep -E '^INDOCKER_' | sed -e 's/^INDOCKER_//') \
16 | --env ELECTRON_CACHE="/root/.cache/electron" \
17 | --env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \
18 | -v ${PWD}:/project \
19 | -v ${PWD}/docker/node_modules:/project/node_modules \
20 | -v ${PWD}/docker/.hak:/project/.hak \
21 | -v ${PWD}/docker/.gnupg:/root/.gnupg \
22 | -v ~/.cache/electron:/root/.cache/electron \
23 | -v ~/.cache/electron-builder:/root/.cache/electron-builder \
24 | "$IMAGE" "$@"
25 |
--------------------------------------------------------------------------------
/scripts/set-version.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S npx tsx
2 |
3 | /*
4 | * Checks for the presence of a webapp, inspects its version and sets the
5 | * version metadata of the package to match.
6 | */
7 |
8 | import { promises as fs } from "node:fs";
9 | import * as asar from "@electron/asar";
10 | import * as childProcess from "node:child_process";
11 | import * as url from "node:url";
12 |
13 | export async function versionFromAsar(): Promise {
14 | try {
15 | await fs.stat("webapp.asar");
16 | } catch {
17 | throw new Error("No 'webapp.asar' found. Run 'yarn run fetch'");
18 | }
19 |
20 | return asar.extractFile("webapp.asar", "version").toString().trim();
21 | }
22 |
23 | export async function setPackageVersion(ver: string): Promise {
24 | // set version in package.json: electron-builder will use this to populate
25 | // all the various version fields
26 | await new Promise((resolve, reject) => {
27 | childProcess.execFile(
28 | process.platform === "win32" ? "yarn.cmd" : "yarn",
29 | [
30 | "version",
31 | "-s",
32 | "--no-git-tag-version", // This also means "don't commit to git" as it turns out
33 | "--new-version",
34 | ver,
35 | ],
36 | {
37 | // We need shell mode on Windows to be able to launch `.cmd` executables
38 | // See https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
39 | shell: process.platform === "win32",
40 | },
41 | (err) => {
42 | if (err) {
43 | reject(err);
44 | } else {
45 | resolve();
46 | }
47 | },
48 | );
49 | });
50 | }
51 |
52 | async function main(args: string[]): Promise {
53 | let version = args[0];
54 |
55 | if (version === undefined) version = await versionFromAsar();
56 |
57 | await setPackageVersion(version);
58 | return 0;
59 | }
60 |
61 | if (import.meta.url.startsWith("file:")) {
62 | const modulePath = url.fileURLToPath(import.meta.url);
63 | if (process.argv[1] === modulePath) {
64 | main(process.argv.slice(2))
65 | .then((ret) => {
66 | process.exit(ret);
67 | })
68 | .catch((e) => {
69 | console.error(e);
70 | process.exit(1);
71 | });
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "resolveJsonModule": true,
4 | "moduleResolution": "node16",
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "target": "es2022",
8 | "module": "node16",
9 | "sourceMap": false,
10 | "strict": true,
11 | "lib": ["es2021"]
12 | },
13 | "include": ["../src/@types", "./**/*.ts"]
14 | }
15 |
--------------------------------------------------------------------------------
/src/@types/global.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021-2024 New Vector Ltd.
3 |
4 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5 | Please see LICENSE files in the repository root for full details.
6 | */
7 |
8 | import { type BrowserWindow } from "electron";
9 |
10 | import type AutoLaunch from "auto-launch";
11 | import { type AppLocalization } from "../language-helper.js";
12 |
13 | // global type extensions need to use var for whatever reason
14 | /* eslint-disable no-var */
15 | declare global {
16 | type IConfigOptions = Record;
17 |
18 | var mainWindow: BrowserWindow | null;
19 | var appQuitting: boolean;
20 | var appLocalization: AppLocalization;
21 | var launcher: AutoLaunch;
22 | var vectorConfig: IConfigOptions;
23 | var trayConfig: {
24 | // eslint-disable-next-line camelcase
25 | icon_path: string;
26 | brand: string;
27 | };
28 | }
29 | /* eslint-enable no-var */
30 |
--------------------------------------------------------------------------------
/src/build-config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2025 New Vector Ltd.
3 |
4 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5 | Please see LICENSE files in the repository root for full details.
6 | */
7 |
8 | import path, { dirname } from "node:path";
9 | import { fileURLToPath } from "node:url";
10 |
11 | import { type JsonObject, loadJsonFile } from "./utils.js";
12 |
13 | const __dirname = dirname(fileURLToPath(import.meta.url));
14 |
15 | interface BuildConfig {
16 | appId: string;
17 | protocol: string;
18 | }
19 |
20 | export function readBuildConfig(): BuildConfig {
21 | const packageJson = loadJsonFile(path.join(__dirname, "..", "package.json")) as JsonObject;
22 | return {
23 | appId: (packageJson["electron_appId"] as string) || "im.riot.app",
24 | protocol: (packageJson["electron_protocol"] as string) || "io.element.desktop",
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/displayMediaCallback.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2023, 2024 New Vector Ltd.
3 |
4 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5 | Please see LICENSE files in the repository root for full details.
6 | */
7 |
8 | import type { Streams } from "electron";
9 |
10 | type DisplayMediaCallback = (streams: Streams) => void;
11 |
12 | let displayMediaCallback: DisplayMediaCallback | null;
13 |
14 | export const getDisplayMediaCallback = (): DisplayMediaCallback | null => {
15 | return displayMediaCallback;
16 | };
17 |
18 | export const setDisplayMediaCallback = (callback: DisplayMediaCallback | null): void => {
19 | displayMediaCallback = callback;
20 | };
21 |
--------------------------------------------------------------------------------
/src/i18n/strings/cs.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Storno",
4 | "close": "Zavřít",
5 | "close_brand": "Zavřít %(brand)s",
6 | "copy": "Zkopírovat",
7 | "cut": "Vyjmout",
8 | "delete": "Smazat",
9 | "edit": "Upravit",
10 | "minimise": "Minimalizovat",
11 | "paste": "Vložit",
12 | "paste_match_style": "Vložit a přizpůsobit styl",
13 | "quit": "Ukončit",
14 | "redo": "Znovu",
15 | "select_all": "Vybrat vše",
16 | "show_hide": "Zobrazit/Skrýt",
17 | "undo": "Zpět",
18 | "zoom_in": "Přiblížit",
19 | "zoom_out": "Oddálit"
20 | },
21 | "common": {
22 | "about": "O",
23 | "brand_help": "%(brand)s nápověda",
24 | "help": "Nápověda",
25 | "no": "Ne",
26 | "preferences": "Předvolby",
27 | "yes": "Ano"
28 | },
29 | "confirm_quit": "Opravdu chcete ukončit aplikaci?",
30 | "edit_menu": {
31 | "speech": "Řeč",
32 | "speech_start_speaking": "Spustit nahrávání hlasu",
33 | "speech_stop_speaking": "Zastavit nahrávání hlasu"
34 | },
35 | "file_menu": {
36 | "label": "Soubor"
37 | },
38 | "menu": {
39 | "hide": "Skrýt",
40 | "hide_others": "Skrýt ostatní",
41 | "services": "Služby",
42 | "unhide": "Zrušit skrytí"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Přidat do slovníku",
46 | "copy_email": "Kopírovat e-mailovou adresu",
47 | "copy_image": "Kopírovat obrázek",
48 | "copy_image_url": "Kopírovat adresu obrázku",
49 | "copy_link_url": "Kopírovat adresu odkazu",
50 | "save_image_as": "Uložit obrázek jako...",
51 | "save_image_as_error_description": "Obrázek se nepodařilo uložit",
52 | "save_image_as_error_title": "Chyba při ukládání obrázku"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Vymazat data a znovu načíst?",
57 | "backend_changed_detail": "Nelze získat přístup k tajnému klíči ze systémové klíčenky, zdá se, že se změnil.",
58 | "backend_changed_title": "Nepodařilo se načíst databázi",
59 | "backend_no_encryption": "Váš systém má podporovanou klíčenku, ale šifrování není k dispozici.",
60 | "backend_no_encryption_detail": "Electron zjistil, že pro vaši klíčenku %(backend)s není k dispozici šifrování. Ujistěte se, že máte nainstalovanou klíčenku. Pokud ji máte, restartujte počítač a zkuste to znovu. Volitelně můžete povolit %(brand)s použít slabší formu šifrování.",
61 | "backend_no_encryption_title": "Bez podpory šifrování",
62 | "unsupported_keyring": "Váš systém má nepodporovanou klíčenku, což znamená, že databázi nelze otevřít.",
63 | "unsupported_keyring_detail": "Detekce klíčenky Electronu nenalezla podporovaný backend. Můžete se pokusit ručně nakonfigurovat backend spuštěním %(brand)s s argumentem příkazového řádku, jednorázovou operací. Viz %(link)s.",
64 | "unsupported_keyring_title": "Systém není podporován",
65 | "unsupported_keyring_use_basic_text": "Používat slabší šifrování",
66 | "unsupported_keyring_use_plaintext": "Nepoužívat žádné šifrování"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Aktuální velikost",
71 | "toggle_developer_tools": "Přepnout zobrazení nástrojů pro vývojáře",
72 | "toggle_full_screen": "Přepnout zobrazení celé obrazovky",
73 | "view": "Zobrazit"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Přenést vše do popředí",
77 | "label": "Okno",
78 | "zoom": "Lupa"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/cy.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Diddymu",
4 | "close": "Cau",
5 | "close_brand": "Cau %(brand)s",
6 | "copy": "Copïo",
7 | "cut": "Torri",
8 | "delete": "Dileu",
9 | "edit": "Golygu",
10 | "minimise": "Lleihau",
11 | "paste": "Gludo",
12 | "paste_match_style": "Arddull Gludo a Chyfateb",
13 | "quit": "Gadael",
14 | "redo": "Ail-wneud",
15 | "select_all": "Dewis y Cyfan",
16 | "show_hide": "Dangos/Cuddio",
17 | "undo": "Dadwneud",
18 | "zoom_in": "Chwyddo i Mewn",
19 | "zoom_out": "Chwyddo Allan"
20 | },
21 | "common": {
22 | "about": "Ynghylch",
23 | "brand_help": "Cymorth %(brand)s",
24 | "help": "Cymorth",
25 | "no": "Na",
26 | "preferences": "Dewisiadau",
27 | "yes": "Iawn"
28 | },
29 | "confirm_quit": "Ydych chi'n siŵr eich bod am roi'r gorau iddi?",
30 | "edit_menu": {
31 | "speech": "Lleferydd",
32 | "speech_start_speaking": "Cychwyn Llefaru",
33 | "speech_stop_speaking": "Peidio Llefaru"
34 | },
35 | "file_menu": {
36 | "label": "Ffeil"
37 | },
38 | "menu": {
39 | "hide": "Cuddio",
40 | "hide_others": "Cuddio'r Gweddill",
41 | "services": "Gwasanaethau",
42 | "unhide": "Datguddio"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Ychwanegu at y geiriadur",
46 | "copy_email": "Copïo cyfeiriad e-bost",
47 | "copy_image": "Copïo delwedd",
48 | "copy_image_url": "Copïo cyfeiriad delwedd",
49 | "copy_link_url": "Copïo cyfeiriad y ddolen",
50 | "save_image_as": "Cadw delwedd fel...",
51 | "save_image_as_error_description": "Methodd cadw'r ddelwedd",
52 | "save_image_as_error_title": "Methodd cadw'r ddelwedd"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Clirio data ac ail-lwytho?",
57 | "backend_changed_detail": "Methu cael mynediad at y gyfrinach o allweddi'r system, mae'n ymddangos ei fod wedi newid.",
58 | "backend_changed_title": "Methwyd llwytho'r gronfa ddata",
59 | "backend_no_encryption": "Mae gan eich system cylch allwedd sy'n cael ei gefnogi ond nid yw amgryptio ar gael.",
60 | "backend_no_encryption_detail": "Mae Electron wedi canfod nad yw amgryptio ar gael ar eich cylch allweddi %(backend)s. Gwnewch yn siŵr bod y cylch allweddi wedi'i osod. Os oes y cylch allweddi wedi'i osod, ail gychwynnwch a cheisiwch eto. Yn ddewisol, gallwch ganiatáu i %(brand)s ddefnyddio ffurf wannach o amgryptio.",
61 | "backend_no_encryption_title": "Dim cefnogaeth amgryptio",
62 | "unsupported_keyring": "Mae gan eich system allweddell nad yw'n cael ei chefnogi sy'n golygu nad oes modd agor y gronfa ddata.",
63 | "unsupported_keyring_detail": "Heb ganfod allweddell Electron gefn. Gallwch geisio ffurfweddu'r gefn â llaw trwy gychwyn %(brand)s gyda dadl llinell orchymyn, gweithrediad untro. Gweler %(link)s.",
64 | "unsupported_keyring_title": "System heb ei chefnogi"
65 | }
66 | },
67 | "view_menu": {
68 | "actual_size": "Maint Gwirioneddol",
69 | "toggle_developer_tools": "Toglo Offer Datblygwyr",
70 | "toggle_full_screen": "Toglo Sgrin Lawn",
71 | "view": "Golwg"
72 | },
73 | "window_menu": {
74 | "bring_all_to_front": "Popeth i'r Blaen",
75 | "label": "Ffenestr",
76 | "zoom": "Chwyddo"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/i18n/strings/de_DE.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Abbrechen",
4 | "close": "Schließen",
5 | "close_brand": "%(brand)s schließen",
6 | "copy": "Kopieren",
7 | "cut": "Ausschneiden",
8 | "delete": "Löschen",
9 | "edit": "Bearbeiten",
10 | "minimise": "Minimieren",
11 | "paste": "Einfügen",
12 | "paste_match_style": "Einfügen und Formatierung beibehalten",
13 | "quit": "Beenden",
14 | "redo": "Wiederherstellen",
15 | "select_all": "Alles auswählen",
16 | "show_hide": "Anzeigen/Ausblenden",
17 | "undo": "Rückgängig",
18 | "zoom_in": "Vergrößern",
19 | "zoom_out": "Verkleinern"
20 | },
21 | "common": {
22 | "about": "Über",
23 | "brand_help": "%(brand)s Hilfe",
24 | "help": "Hilfe",
25 | "no": "Nein",
26 | "preferences": "Präferenzen",
27 | "yes": "Ja"
28 | },
29 | "confirm_quit": "Wirklich beenden?",
30 | "edit_menu": {
31 | "speech": "Sprache",
32 | "speech_start_speaking": "Aufnahme starten",
33 | "speech_stop_speaking": "Aufnahme beenden"
34 | },
35 | "file_menu": {
36 | "label": "Datei"
37 | },
38 | "menu": {
39 | "hide": "Verstecken",
40 | "hide_others": "Andere verstecken",
41 | "services": "Dienste",
42 | "unhide": "Wieder anzeigen"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Wörterbuch hinzufügen",
46 | "copy_email": "Email-Adresse kopieren",
47 | "copy_image": "Bild kopieren",
48 | "copy_image_url": "Bild-Adresse kopieren",
49 | "copy_link_url": "Link-Adresse kopieren",
50 | "save_image_as": "Bild speichern unter...",
51 | "save_image_as_error_description": "Das Bild konnte nicht gespeichert werden",
52 | "save_image_as_error_title": "Bild kann nicht gespeichert werden"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Daten löschen und neu laden?",
57 | "backend_changed_detail": "Zugriff auf Schlüssel im Systemschlüsselbund nicht möglich, er scheint sich geändert zu haben.",
58 | "backend_changed_title": "Datenbank konnte nicht geladen werden",
59 | "backend_no_encryption": "Ihr System verfügt über einen unterstützten Keyring, aber die Verschlüsselung ist nicht verfügbar.",
60 | "backend_no_encryption_detail": "Electron hat festgestellt, dass Verschlüsselung in Ihrem Keyring %(backend)s nicht verfügbar ist. Bitte stellen Sie sicher, dass Sie den Keyringinstalliert haben. Wenn Sie den Keyring installiert haben, starten Sie ihn bitte neu und versuchen Sie es erneut. Optional können Sie die Verwendung einer schwächeren Form der Verschlüsselung zulassen %(brand)s.",
61 | "backend_no_encryption_title": "Keine Verschlüsselungsunterstützung",
62 | "unsupported_keyring": "Der Schlüsselbund ihres Systems wird nicht unterstützt, wodurch die Datenbank nicht geöffnet werden kann.",
63 | "unsupported_keyring_detail": "Die Schlüsselbunderkennung von Electron hat kein unterstütztes Backend gefunden. Möglicherweise können sie dennoch den ihres Systemes verwenden. Infos unter %(link)s.",
64 | "unsupported_keyring_title": "System nicht unterstützt",
65 | "unsupported_keyring_use_basic_text": "Schwächere Verschlüsselung verwenden",
66 | "unsupported_keyring_use_plaintext": "Verwenden Sie keine Verschlüsselung"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Tatsächliche Größe",
71 | "toggle_developer_tools": "Developer-Tools an/aus",
72 | "toggle_full_screen": "Vollbildschirm an/aus",
73 | "view": "Ansicht"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Alles in den Vordergrund",
77 | "label": "Fenster",
78 | "zoom": "Zoomen"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/el.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Ακύρωση",
4 | "close": "Κλείσιμο",
5 | "close_brand": "Κλείσιμο %(brand)s",
6 | "copy": "Αντιγραφή",
7 | "cut": "Αποκοπή",
8 | "delete": "Διαγραφή",
9 | "edit": "Επεξεργασία",
10 | "minimise": "Ελαχιστοποίηση",
11 | "paste": "Επικόλληση",
12 | "paste_match_style": "Επικόλληση και Ταίριασμα Στυλ",
13 | "quit": "Κλείσιμο",
14 | "redo": "Επανάληψη",
15 | "select_all": "Επιλογή Όλων",
16 | "show_hide": "Eμφάνιση/Απόκρυψη",
17 | "undo": "Αναίρεση",
18 | "zoom_in": "Μεγέθυνση",
19 | "zoom_out": "Σμίκρυνση"
20 | },
21 | "common": {
22 | "about": "Σχετικά με",
23 | "brand_help": "%(brand)s Υποστήριξη",
24 | "help": "Βοήθεια",
25 | "preferences": "Προτιμήσεις"
26 | },
27 | "confirm_quit": "Είστε βέβαιος ότι θέλετε να εγκαταλείψετε;",
28 | "edit_menu": {
29 | "speech": "Ομιλία",
30 | "speech_start_speaking": "Ξεκινήστε να μιλάτε",
31 | "speech_stop_speaking": "Τερματίστε να μιλάτε"
32 | },
33 | "file_menu": {
34 | "label": "Αρχείο"
35 | },
36 | "menu": {
37 | "hide": "Απόκρυψη",
38 | "hide_others": "Απόκρυψη Άλλων",
39 | "services": "Υπηρεσίες",
40 | "unhide": "Εμφάνιση"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Προσθήκη στο λεξικό",
44 | "copy_email": "Αντιγραφή διεύθυνσης email",
45 | "copy_image": "Αντιγραφή εικόνας",
46 | "copy_image_url": "Αντιγραφή διεύθυνσης εικόνας",
47 | "copy_link_url": "Αντιγραφή διεύθυνσης συνδέσμου",
48 | "save_image_as": "Αποθήκευση εικόνας ως...",
49 | "save_image_as_error_description": "Η αποθήκευση της εικόνας απέτυχε",
50 | "save_image_as_error_title": "Αποτυχία αποθήκευσης εικόνας"
51 | },
52 | "view_menu": {
53 | "actual_size": "Πραγματικό Μέγεθος",
54 | "toggle_developer_tools": "Άνοιγμα Εργαλείων Προγραμματιστή",
55 | "toggle_full_screen": "Εναλλαγή σε Πλήρη Οθόνη",
56 | "view": "Προβολή"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Μεταφορά Όλων στο Προσκήνιο",
60 | "label": "Παράθυρο",
61 | "zoom": "Ζουμ"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/en_EN.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Cancel",
4 | "close": "Close",
5 | "close_brand": "Close %(brand)s",
6 | "copy": "Copy",
7 | "cut": "Cut",
8 | "delete": "Delete",
9 | "edit": "Edit",
10 | "minimise": "Minimize",
11 | "paste": "Paste",
12 | "paste_match_style": "Paste and Match Style",
13 | "quit": "Quit",
14 | "redo": "Redo",
15 | "select_all": "Select All",
16 | "show_hide": "Show/Hide",
17 | "undo": "Undo",
18 | "zoom_in": "Zoom In",
19 | "zoom_out": "Zoom Out"
20 | },
21 | "common": {
22 | "about": "About",
23 | "brand_help": "%(brand)s Help",
24 | "help": "Help",
25 | "no": "No",
26 | "preferences": "Preferences",
27 | "yes": "Yes"
28 | },
29 | "confirm_quit": "Are you sure you want to quit?",
30 | "edit_menu": {
31 | "speech": "Speech",
32 | "speech_start_speaking": "Start Speaking",
33 | "speech_stop_speaking": "Stop Speaking"
34 | },
35 | "file_menu": {
36 | "label": "File"
37 | },
38 | "menu": {
39 | "hide": "Hide",
40 | "hide_others": "Hide Others",
41 | "services": "Services",
42 | "unhide": "Unhide"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Add to dictionary",
46 | "copy_email": "Copy email address",
47 | "copy_image": "Copy image",
48 | "copy_image_url": "Copy image address",
49 | "copy_link_url": "Copy link address",
50 | "save_image_as": "Save image as...",
51 | "save_image_as_error_description": "The image failed to save",
52 | "save_image_as_error_title": "Failed to save image"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Clear data and reload?",
57 | "backend_changed_detail": "Unable to access secret from system keyring, it appears to have changed.",
58 | "backend_changed_title": "Failed to load database",
59 | "backend_no_encryption": "Your system has a supported keyring but encryption is not available.",
60 | "backend_no_encryption_detail": "Electron has detected that encryption is not available on your keyring %(backend)s. Please ensure that you have the keyring installed. If you do have the keyring installed, please reboot and try again. Optionally, you can allow %(brand)s to use a weaker form of encryption.",
61 | "backend_no_encryption_title": "No encryption support",
62 | "unsupported_keyring": "Your system has an unsupported keyring meaning the database cannot be opened.",
63 | "unsupported_keyring_detail": "Electron's keyring detection did not find a supported backend. You can attempt to manually configure the backend by starting %(brand)s with a command-line argument, a one-time operation. See %(link)s.",
64 | "unsupported_keyring_title": "System unsupported",
65 | "unsupported_keyring_use_basic_text": "Use weaker encryption",
66 | "unsupported_keyring_use_plaintext": "Use no encryption"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Actual Size",
71 | "toggle_developer_tools": "Toggle Developer Tools",
72 | "toggle_full_screen": "Toggle Full Screen",
73 | "view": "View"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Bring All to Front",
77 | "label": "Window",
78 | "zoom": "Zoom"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/eo.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Nuligi",
4 | "close": "Fermi",
5 | "close_brand": "Fermu %(brand)s",
6 | "copy": "Kopii",
7 | "cut": "Tranĉi",
8 | "delete": "Forigi",
9 | "edit": "Redakti",
10 | "minimise": "Minimumigi",
11 | "paste": "Enmeti",
12 | "redo": "Refari",
13 | "select_all": "Elekti Ĉiujn",
14 | "show_hide": "Montri/Kaŝi",
15 | "undo": "Malfari"
16 | },
17 | "common": {
18 | "about": "Prio",
19 | "help": "Helpo",
20 | "preferences": "Agordoj"
21 | },
22 | "confirm_quit": "Ĉu vi certas, ke vi volas ĉesi?",
23 | "edit_menu": {
24 | "speech_start_speaking": "Ekparoli",
25 | "speech_stop_speaking": "Ĉesi Paroli"
26 | },
27 | "file_menu": {
28 | "label": "Dosiero"
29 | },
30 | "menu": {
31 | "hide": "Kaŝi",
32 | "hide_others": "Kaŝi Aliajn",
33 | "unhide": "Malkaŝi"
34 | },
35 | "right_click_menu": {
36 | "copy_email": "Kopiu retadreson",
37 | "copy_image": "Kopiu bildon",
38 | "copy_image_url": "Kopiu adreson de la bildo",
39 | "copy_link_url": "Kopiu ligilon de la bildo",
40 | "save_image_as_error_description": "La bildo malsukcesis elŝutiĝi",
41 | "save_image_as_error_title": "Malsukcesis elŝuti bildon"
42 | },
43 | "view_menu": {
44 | "toggle_developer_tools": "Baskuligi Programistajn Ilojn",
45 | "view": "Vidi"
46 | },
47 | "window_menu": {
48 | "label": "Fenestro"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/i18n/strings/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Cancelar",
4 | "close": "Cerrar",
5 | "close_brand": "Cerrar %(brand)s",
6 | "copy": "Copiar",
7 | "cut": "Cortar",
8 | "delete": "Borrar",
9 | "edit": "Editar",
10 | "minimise": "Minimizar",
11 | "paste": "Pegar",
12 | "paste_match_style": "Pegar manteniendo estilo",
13 | "quit": "Salir",
14 | "redo": "Rehacer",
15 | "select_all": "Seleccionar todo",
16 | "show_hide": "Ver/Ocultar",
17 | "undo": "Deshacer",
18 | "zoom_in": "Acercar",
19 | "zoom_out": "Alejar"
20 | },
21 | "common": {
22 | "about": "Acerca de",
23 | "brand_help": "Ayuda sobre %(brand)s",
24 | "help": "Ayuda",
25 | "preferences": "Preferencias"
26 | },
27 | "confirm_quit": "¿Quieres salir?",
28 | "edit_menu": {
29 | "speech": "Dictado",
30 | "speech_start_speaking": "Empezar a hablar",
31 | "speech_stop_speaking": "Parar de hablar"
32 | },
33 | "file_menu": {
34 | "label": "Archivo"
35 | },
36 | "menu": {
37 | "hide": "Ocultar",
38 | "hide_others": "Ocultar otros",
39 | "services": "Servicios",
40 | "unhide": "Mostrar"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Añadir al diccionario",
44 | "copy_email": "Copiar dirección de correo",
45 | "copy_image": "Copiar imagen",
46 | "copy_image_url": "Copiar dirección de la imagen",
47 | "copy_link_url": "Copiar dirección de enlace",
48 | "save_image_as": "Guardar imagen como...",
49 | "save_image_as_error_description": "La imagen no se ha podido guardar",
50 | "save_image_as_error_title": "No se ha podido guardar la imagen"
51 | },
52 | "view_menu": {
53 | "actual_size": "Tamaño real",
54 | "toggle_developer_tools": "Abrir/cerrar herramientas de desarrollo",
55 | "toggle_full_screen": "Entrar/salir de pantalla completa",
56 | "view": "Ver"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Traer todas al primer plano",
60 | "label": "Ventana"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/i18n/strings/et.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Loobu",
4 | "close": "Sulge",
5 | "close_brand": "Sulge %(brand)s",
6 | "copy": "Kopeeri",
7 | "cut": "Lõika",
8 | "delete": "Kustuta",
9 | "edit": "Muuda",
10 | "minimise": "Vähenda",
11 | "paste": "Aseta",
12 | "paste_match_style": "Aseta kasutades sama stiili",
13 | "quit": "Välju",
14 | "redo": "Tee uuesti",
15 | "select_all": "Vali kõik",
16 | "show_hide": "Näita/peida",
17 | "undo": "Võta tagasi",
18 | "zoom_in": "Suurenda",
19 | "zoom_out": "Vähenda"
20 | },
21 | "common": {
22 | "about": "Rakenduse teave",
23 | "brand_help": "%(brand)s abiteave",
24 | "help": "Abiteave",
25 | "no": "Ei",
26 | "preferences": "Eelistused",
27 | "yes": "Jah"
28 | },
29 | "confirm_quit": "Kas sa kindlasti soovid rakendusest väljuda?",
30 | "edit_menu": {
31 | "speech": "Kõne",
32 | "speech_start_speaking": "Alusta rääkimist",
33 | "speech_stop_speaking": "Lõpeta rääkimine"
34 | },
35 | "file_menu": {
36 | "label": "Fail"
37 | },
38 | "menu": {
39 | "hide": "Peida",
40 | "hide_others": "Peida muud",
41 | "services": "Teenused",
42 | "unhide": "Näita uuesti"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Lisa sõnastikku",
46 | "copy_email": "Kopeeri e-posti aadress",
47 | "copy_image": "Kopeeri pilt",
48 | "copy_image_url": "Kopeeri pildi aadress",
49 | "copy_link_url": "Kopeeri lingi aadress",
50 | "save_image_as": "Salvesta pilt kui...",
51 | "save_image_as_error_description": "Seda pilti ei õnnestunud salvestada",
52 | "save_image_as_error_title": "Pildi salvestamine ei õnnestunud"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Kas kustutame andmed ja laadime uuesti?",
57 | "backend_changed_detail": "Süsteemsest võtmerõngast ei õnnestu laadida vajalikku saladust, tundub et ta on muutunud.",
58 | "backend_changed_title": "Andmebaasi ei õnnestunud laadida",
59 | "backend_no_encryption": "Sinu süsteem kasutab toetatud võtmerõngast, kuid krüptimist pole saadaval.",
60 | "backend_no_encryption_detail": "Electron on tuvastanud, et krüptimine pole sinu %(backend)s võtmerõnga jaoks saadaval. Palun kontrolli, et võtmerõngas oleks korrektselt paigaldatud. Kui sul on võtmerõngas paigaldatud, siis palun taaskäivita ta ja proovi uuesti. Lisavõimalusena saad lubada, et %(brand)s kasutab nõrgemat krüptimislahendust.",
61 | "backend_no_encryption_title": "Krüptimise tugi puudub",
62 | "unsupported_keyring": "Sinu süsteemis on kasutusel mittetoetatud võtmerõnga versioon ning see tähendab, et andmebaasi ei saa avada.",
63 | "unsupported_keyring_detail": "Electroni võtmerõnga tuvastamine ei leidnud toetatud taustateenust. Kui käivitad rakenduse %(brand)s käsurealt õigete argumentidega, siis võib taustateenuse käsitsi seadistamine õnnestuda ning seda tegevust peaksid vaid üks kord tegema. Lisateave: %(link)s.",
64 | "unsupported_keyring_title": "Süsteem pole toetatud",
65 | "unsupported_keyring_use_basic_text": "Kasuta nõrgemat krüptimist",
66 | "unsupported_keyring_use_plaintext": "Ära üldse kasuta krüptimist"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Näita tavasuuruses",
71 | "toggle_developer_tools": "Arendaja töövahendid sisse/välja",
72 | "toggle_full_screen": "Täisekraanivaade sisse/välja",
73 | "view": "Näita"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Too kõik esiplaanile",
77 | "label": "Aken",
78 | "zoom": "Suumi"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/fa.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "لغو",
4 | "close": "بستن",
5 | "close_brand": "بستن %(brand)s",
6 | "copy": "رونوشت",
7 | "cut": "برش",
8 | "delete": "پاککردن",
9 | "edit": "ویرایش",
10 | "minimise": "کمینه",
11 | "paste": "جایگذاری",
12 | "paste_match_style": "جایگذاری و تطبیق سَبک",
13 | "quit": "خروج",
14 | "redo": "انجام دوباره",
15 | "select_all": "گزینش همه",
16 | "show_hide": "نمایش/پنهان",
17 | "undo": "بازگردانی",
18 | "zoom_in": "بزرگنمایی به داخل",
19 | "zoom_out": "بزرگنمایی به خارج"
20 | },
21 | "common": {
22 | "about": "درباره",
23 | "brand_help": "کمک %(brand)s",
24 | "help": "راهنما",
25 | "preferences": "ترجیحات"
26 | },
27 | "confirm_quit": "آیا مطمئنید که میخواهید خارج شوید؟",
28 | "edit_menu": {
29 | "speech": "صحبت کردن",
30 | "speech_start_speaking": "صحبت کردن را شروع کنید",
31 | "speech_stop_speaking": "صحبت کردن را تمام کنید"
32 | },
33 | "file_menu": {
34 | "label": "پرونده"
35 | },
36 | "menu": {
37 | "hide": "پنهان",
38 | "hide_others": "پنهان کردن دیگران",
39 | "services": "خدمات",
40 | "unhide": "آشکار"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "افزودن به لغتنامه",
44 | "copy_email": "رونوشت نشانی رایانامه",
45 | "copy_image": "رونوشت تصویر",
46 | "copy_image_url": "رونوشت نشانی تصویر",
47 | "copy_link_url": "رونوشت نشانی پیوند",
48 | "save_image_as": "ذخیرهٔ تصویر به عنوان...",
49 | "save_image_as_error_description": "تصویر ذخیره نشد",
50 | "save_image_as_error_title": "ذخیرهٔ تصویر شکست خورد"
51 | },
52 | "view_menu": {
53 | "actual_size": "اندازهٔ واقعی",
54 | "toggle_developer_tools": "تغییر وضعیت ابزارهای توسعهدهنده",
55 | "toggle_full_screen": "تغییر وضعیت تمامصفحه",
56 | "view": "مشاهده"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "همه را به جلو بیاورید",
60 | "label": "پنجره",
61 | "zoom": "بزرگنمایی"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/fi.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Peruuta",
4 | "close": "Sulje",
5 | "close_brand": "Sulje %(brand)s",
6 | "copy": "Kopioi",
7 | "cut": "Leikkaa",
8 | "delete": "Poista",
9 | "edit": "Muokkaa",
10 | "minimise": "Pienennä",
11 | "paste": "Liitä",
12 | "paste_match_style": "Liitä ja sovita tyyli",
13 | "quit": "Lopeta",
14 | "redo": "Tee uudestaan",
15 | "select_all": "Valitse kaikki",
16 | "show_hide": "Näytä/piilota",
17 | "undo": "Peru",
18 | "zoom_in": "Suurenna",
19 | "zoom_out": "Pienennä"
20 | },
21 | "common": {
22 | "about": "Tietoa",
23 | "brand_help": "%(brand)s-tuki",
24 | "help": "Ohje",
25 | "no": "Ei",
26 | "preferences": "Valinnat",
27 | "yes": "Kyllä"
28 | },
29 | "confirm_quit": "Haluatko varmasti poistua?",
30 | "edit_menu": {
31 | "speech": "Puhe",
32 | "speech_start_speaking": "Aloita puhe",
33 | "speech_stop_speaking": "Lopeta puhe"
34 | },
35 | "file_menu": {
36 | "label": "Tiedosto"
37 | },
38 | "menu": {
39 | "hide": "Piilota",
40 | "hide_others": "Piilota muut",
41 | "services": "Palvelut",
42 | "unhide": "Palauta näkyviin"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Lisää sanakirjaan",
46 | "copy_email": "Kopioi sähköpostiosoite",
47 | "copy_image": "Kopioi kuva",
48 | "copy_image_url": "Kopioi kuvan osoite",
49 | "copy_link_url": "Kopioi linkin osoite",
50 | "save_image_as": "Tallenna kuva nimellä...",
51 | "save_image_as_error_description": "Kuvan tallennus epäonnistui",
52 | "save_image_as_error_title": "Kuvan tallennus epäonnistui"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed_title": "Tietokannan lataaminen epäonnistui",
57 | "unsupported_keyring_title": "Järjestelmä ei ole tuettu"
58 | }
59 | },
60 | "view_menu": {
61 | "actual_size": "Alkuperäinen koko",
62 | "toggle_developer_tools": "Näytä tai piilota kehittäjätyökalut",
63 | "toggle_full_screen": "Vaihda koko näytön tilaa",
64 | "view": "Näytä"
65 | },
66 | "window_menu": {
67 | "bring_all_to_front": "Tuo kaikki eteen",
68 | "label": "Ikkuna",
69 | "zoom": "Suurennus"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/i18n/strings/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Annuler",
4 | "close": "Fermer",
5 | "close_brand": "Fermer %(brand)s",
6 | "copy": "Copier",
7 | "cut": "Couper",
8 | "delete": "Supprimer",
9 | "edit": "Modifier",
10 | "minimise": "Minimiser",
11 | "paste": "Coller",
12 | "paste_match_style": "Copier avec le style de destination",
13 | "quit": "Quitter",
14 | "redo": "Refaire",
15 | "select_all": "Tout sélectionner",
16 | "show_hide": "Afficher/Masquer",
17 | "undo": "Annuler",
18 | "zoom_in": "Zoomer",
19 | "zoom_out": "Dé-zoomer"
20 | },
21 | "common": {
22 | "about": "À propos",
23 | "brand_help": "Aide de %(brand)s",
24 | "help": "Aide",
25 | "no": "Non",
26 | "preferences": "Préférences",
27 | "yes": "Oui"
28 | },
29 | "confirm_quit": "Êtes-vous sûr de vouloir quitter ?",
30 | "edit_menu": {
31 | "speech": "Dictée",
32 | "speech_start_speaking": "Commencer la dictée",
33 | "speech_stop_speaking": "Arrêter la dictée"
34 | },
35 | "file_menu": {
36 | "label": "Fichier"
37 | },
38 | "menu": {
39 | "hide": "Masquer",
40 | "hide_others": "Masquer les autres",
41 | "services": "Services",
42 | "unhide": "Dé-masquer"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Ajouter au dictionnaire",
46 | "copy_email": "Copier l’adresse e-mail",
47 | "copy_image": "Copier l’image",
48 | "copy_image_url": "Copier l'adresse de l'image",
49 | "copy_link_url": "Copier l’adresse du lien",
50 | "save_image_as": "Enregistrer l’image sous…",
51 | "save_image_as_error_description": "L’image n’a pas pu être sauvegardée",
52 | "save_image_as_error_title": "Échec de la sauvegarde de l’image"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Effacer les données et recharger ?",
57 | "backend_changed_detail": "Impossible d'accéder aux secrets depuis le trousseau de clés du système, il semble avoir changé.",
58 | "backend_changed_title": "Impossible de charger la base de données",
59 | "backend_no_encryption": "Votre système dispose d'un trousseau de clés compatible mais le chiffrement est indisponible.",
60 | "backend_no_encryption_detail": "Electron a détecté que le chiffrement n'est pas disponible pour votre trousseau de clés %(backend)s. Assurez-vous que votre trousseau de clés est installé. Si le trousseau de clés est installé, redémarrez et réessayez. Vous pouvez éventuellement autoriser %(brand)s l'utilisation d'une forme de chiffrement plus faible.",
61 | "backend_no_encryption_title": "Aucune prise en charge du chiffrement",
62 | "unsupported_keyring": "Votre système possède un trousseau de clés non pris en charge, la base de données ne peut pas être ouverte.",
63 | "unsupported_keyring_detail": "La détection du porte-clés par Electron n'a pas permis de trouver de backend compatible. Vous pouvez essayer de configurer manuellement le backend en utilisant %(brand)s avec un argument de ligne de commande. Cette opération doit être effectuer une seule fois. Voir%(link)s.",
64 | "unsupported_keyring_title": "Système non pris en charge",
65 | "unsupported_keyring_use_basic_text": "Utiliser un chiffrement plus faible",
66 | "unsupported_keyring_use_plaintext": "N'utilise pas de chiffrement"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Taille réelle",
71 | "toggle_developer_tools": "Basculer les outils de développement",
72 | "toggle_full_screen": "Basculer le plein écran",
73 | "view": "Afficher"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Tout amener au premier plan",
77 | "label": "Fenêtre",
78 | "zoom": "Zoom"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/gl.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Cancelar",
4 | "close": "Pechar",
5 | "copy": "Copiar",
6 | "cut": "Cortar",
7 | "delete": "Eliminar",
8 | "edit": "Editar",
9 | "minimise": "Minimizar",
10 | "paste": "Pegar",
11 | "paste_match_style": "Pegar e imitar estilo",
12 | "quit": "Saír",
13 | "redo": "Refacer",
14 | "select_all": "Elexir todo",
15 | "show_hide": "Mostrar/Agochar",
16 | "undo": "Desfacer",
17 | "zoom_in": "Aumentar",
18 | "zoom_out": "Diminuir"
19 | },
20 | "common": {
21 | "about": "Acerca de",
22 | "help": "Axuda",
23 | "preferences": "Preferencias"
24 | },
25 | "confirm_quit": "Tes a certeza de que queres saír?",
26 | "edit_menu": {
27 | "speech": "Falar",
28 | "speech_start_speaking": "Comeza a falar",
29 | "speech_stop_speaking": "Deixa de falar"
30 | },
31 | "file_menu": {
32 | "label": "Ficheiro"
33 | },
34 | "menu": {
35 | "hide": "Agochar",
36 | "hide_others": "Agochar outras",
37 | "services": "Servizos",
38 | "unhide": "Desagochar"
39 | },
40 | "right_click_menu": {
41 | "add_to_dictionary": "Engadir ao dicionario",
42 | "copy_email": "Copiar enderezo de email",
43 | "copy_image": "Copiar imaxe",
44 | "copy_link_url": "Copiar enderezo da ligazón",
45 | "save_image_as": "Gardar imaxe como...",
46 | "save_image_as_error_description": "Non se gardou a imaxe",
47 | "save_image_as_error_title": "Fallou o gardado da imaxe"
48 | },
49 | "view_menu": {
50 | "actual_size": "Tamaño real",
51 | "toggle_developer_tools": "Activar ferramentas de desenvolvemento",
52 | "toggle_full_screen": "Activar pantalla completa",
53 | "view": "Vista"
54 | },
55 | "window_menu": {
56 | "bring_all_to_front": "Traer todo á fronte",
57 | "label": "Ventá",
58 | "zoom": "Aumento"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/i18n/strings/he.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "ביטול",
4 | "close": "סגור",
5 | "close_brand": "סגור%(brand)s",
6 | "copy": "העתק",
7 | "cut": "גזור",
8 | "delete": "מחק",
9 | "edit": "ערוך",
10 | "minimise": "מזער",
11 | "paste": "הדבק",
12 | "paste_match_style": "הדבק והתאם סגנון",
13 | "quit": "יציאה",
14 | "redo": "בצע שוב",
15 | "select_all": "בחר הכל",
16 | "show_hide": "הצג\\הסתר",
17 | "undo": "בטל ביצוע",
18 | "zoom_in": "התקרב",
19 | "zoom_out": "התרחק"
20 | },
21 | "common": {
22 | "about": "אודות",
23 | "brand_help": "%(brand)s עזרה",
24 | "help": "עזרה",
25 | "preferences": "העדפות"
26 | },
27 | "confirm_quit": "האם אתה בטוח שברצונך לצאת?",
28 | "edit_menu": {
29 | "speech": "דיבור",
30 | "speech_start_speaking": "התחל לדבר",
31 | "speech_stop_speaking": "הפסק לדבר"
32 | },
33 | "file_menu": {
34 | "label": "קובץ"
35 | },
36 | "menu": {
37 | "hide": "הסתר",
38 | "hide_others": "הסתר אחרים",
39 | "services": "שרותים",
40 | "unhide": "בטל הסתרה"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "הוסף למילון",
44 | "copy_email": "העתק כתובת אימייל",
45 | "copy_image": "העתק תמונה",
46 | "copy_image_url": "העתקת כתובת התמונה",
47 | "copy_link_url": "העתק קישור",
48 | "save_image_as": "שמור תמונה בשם...",
49 | "save_image_as_error_description": "שמירת התמונה נכשלה",
50 | "save_image_as_error_title": "נכשל בשמירת התמונה"
51 | },
52 | "view_menu": {
53 | "actual_size": "גודל ממשי",
54 | "toggle_developer_tools": "הפעל כלי מפתחים",
55 | "toggle_full_screen": "הפעל מצב מסך מלא",
56 | "view": "צפה"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "הבא הכל לחזית",
60 | "label": "חלון",
61 | "zoom": "גודל תצוגה"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/hu.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Mégse",
4 | "close": "Bezárás",
5 | "close_brand": "Az %(brand)s bezárása",
6 | "copy": "Másolás",
7 | "cut": "Kivágás",
8 | "delete": "Törlés",
9 | "edit": "Szerkesztés",
10 | "minimise": "Lecsukás",
11 | "paste": "Beillesztés",
12 | "paste_match_style": "Beillesztés formázással",
13 | "quit": "Kilépés",
14 | "redo": "Újra",
15 | "select_all": "Összes kijelölése",
16 | "show_hide": "Megjelenítés/elrejtés",
17 | "undo": "Visszavonás",
18 | "zoom_in": "Nagyítás",
19 | "zoom_out": "Kicsinyítés"
20 | },
21 | "common": {
22 | "about": "Névjegy",
23 | "brand_help": "%(brand)s Súgó",
24 | "help": "Súgó",
25 | "no": "Nem",
26 | "preferences": "Beállítások",
27 | "yes": "Igen"
28 | },
29 | "confirm_quit": "Biztos, hogy kilép?",
30 | "edit_menu": {
31 | "speech": "Beszéd",
32 | "speech_start_speaking": "Kezdjen beszélni",
33 | "speech_stop_speaking": "Fejezze be a beszédet"
34 | },
35 | "file_menu": {
36 | "label": "Fájl"
37 | },
38 | "menu": {
39 | "hide": "Elrejtés",
40 | "hide_others": "Mások elrejtése",
41 | "services": "Szolgáltatás",
42 | "unhide": "Felfedés"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Hozzáadás a szótárhoz",
46 | "copy_email": "E-mail-cím másolása",
47 | "copy_image": "Kép másolása",
48 | "copy_image_url": "Kép címének másolása",
49 | "copy_link_url": "Hivatkozás másolása",
50 | "save_image_as": "Kép mentése másként…",
51 | "save_image_as_error_description": "A kép mentése sikertelen",
52 | "save_image_as_error_title": "Kép mentése sikertelen"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Adatok törlése és újratöltés?",
57 | "backend_changed_detail": "Nem sikerült hozzáférni a rendszerkulcstartó titkos kódjához, úgy tűnik, megváltozott.",
58 | "backend_changed_title": "Nem sikerült betölteni az adatbázist",
59 | "backend_no_encryption": "A rendszer támogatott kulcstartóval rendelkezik, de a titkosítás nem érhető el.",
60 | "backend_no_encryption_title": "Nincs titkosítási támogatás",
61 | "unsupported_keyring": "A rendszer nem támogatott kulcstartóval rendelkezik, ami azt jelenti, hogy az adatbázis nem nyitható meg.",
62 | "unsupported_keyring_detail": "Az Electron kulcstartóészlelése nem talált támogatott háttérrendszert. Megpróbálhatja kézileg beállítani a háttérrendszert az %(brand)s egyszeri, parancssori argumentummal való indításával. Lásd: %(link)s.",
63 | "unsupported_keyring_title": "A rendszer nem támogatott",
64 | "unsupported_keyring_use_basic_text": "Gyengébb titkosítás használata",
65 | "unsupported_keyring_use_plaintext": "Ne használjon titkosítást"
66 | }
67 | },
68 | "view_menu": {
69 | "actual_size": "Jelenlegi méret",
70 | "toggle_developer_tools": "Fejlesztői eszközök",
71 | "toggle_full_screen": "Teljes képernyő",
72 | "view": "Megtekintés"
73 | },
74 | "window_menu": {
75 | "bring_all_to_front": "Minden előtérbe hozása",
76 | "label": "Ablak",
77 | "zoom": "Nagyítás"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/i18n/strings/id.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Batalkan",
4 | "close": "Tutup",
5 | "close_brand": "Tutuo %(brand)s",
6 | "copy": "Salin",
7 | "cut": "Potong",
8 | "delete": "Hapus",
9 | "edit": "Sunting",
10 | "minimise": "Minimalkan",
11 | "paste": "Tempel",
12 | "paste_match_style": "Tempel dan Cocokkan Gaya",
13 | "quit": "Keluar",
14 | "redo": "Ulangi",
15 | "select_all": "Pilih Semua",
16 | "show_hide": "Tampilkan/Sembunyikan",
17 | "undo": "Urungkan",
18 | "zoom_in": "Perbesar",
19 | "zoom_out": "Perkecil"
20 | },
21 | "common": {
22 | "about": "Tentang",
23 | "brand_help": "Bantuan %(brand)s",
24 | "help": "Bantuan",
25 | "no": "Tidak",
26 | "preferences": "Preferensi",
27 | "yes": "Ya"
28 | },
29 | "confirm_quit": "Apakah Anda yakin ingin keluar?",
30 | "edit_menu": {
31 | "speech": "Dikte",
32 | "speech_start_speaking": "Mulai Berbicara",
33 | "speech_stop_speaking": "Berhenti Berbicara"
34 | },
35 | "file_menu": {
36 | "label": "Berkas"
37 | },
38 | "menu": {
39 | "hide": "Sembunyikan",
40 | "hide_others": "Sembunyikan yang Lain",
41 | "services": "Layanan",
42 | "unhide": "Tampilkan"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Tambah ke kamus",
46 | "copy_email": "Salin surel",
47 | "copy_image": "Salin gambar",
48 | "copy_image_url": "Salin alamat gambar",
49 | "copy_link_url": "Salin alamat tautan",
50 | "save_image_as": "Simpan gambar sebagai...",
51 | "save_image_as_error_description": "Gambar gagal disimpan",
52 | "save_image_as_error_title": "Gagal menyimpan gambar"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Hapus data dan muat ulang?",
57 | "backend_changed_detail": "Tidak dapat mengakses rahasia dari keyring sistem, tampaknya telah berubah.",
58 | "backend_changed_title": "Gagal memuat basis data",
59 | "backend_no_encryption": "Sistem Anda memiliki keyring yang didukung tetapi enkripsi tidak tersedia.",
60 | "backend_no_encryption_detail": "Electron telah mendeteksi bahwa enkripsi tidak tersedia pada keyring %(backend)s Anda. Harap pastikan bahwa Anda telah memasang keyring. Jika Anda telah memasang keyring, silakan mulai ulang dan coba lagi. Secara opsional, Anda dapat mengizinkan %(brand)s untuk menggunakan bentuk enkripsi yang lebih lemah.",
61 | "backend_no_encryption_title": "Tidak ada dukungan enkripsi",
62 | "unsupported_keyring": "Sistem Anda memiliki keyring yang tidak didukung yang berarti basis data tidak dapat dibuka.",
63 | "unsupported_keyring_detail": "Deteksi keyring Electron tidak menemukan backend yang didukung. Anda dapat mencoba mengonfigurasi backend secara manual dengan memulai %(brand)s dengan argumen baris perintah, operasi satu kali. Lihat %(link)s.",
64 | "unsupported_keyring_title": "Sistem tidak didukung",
65 | "unsupported_keyring_use_basic_text": "Gunakan enkripsi yang lebih lemah",
66 | "unsupported_keyring_use_plaintext": "Jangan gunakan enkripsi"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Ukuran Sebenarnya",
71 | "toggle_developer_tools": "Beralih Alat Pengembang",
72 | "toggle_full_screen": "Beralih Layar Penuh",
73 | "view": "Tampilan"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Bawa Semua ke Depan",
77 | "label": "Jendela",
78 | "zoom": "Perbesar"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/is.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Hætta við",
4 | "close": "Loka",
5 | "close_brand": "Loka %(brand)s",
6 | "copy": "Afrita",
7 | "cut": "Klippa",
8 | "delete": "Eyða",
9 | "edit": "Breyta",
10 | "minimise": "Lágmarka",
11 | "paste": "Líma",
12 | "paste_match_style": "Líma og samsvara stíl",
13 | "quit": "Hætta",
14 | "redo": "Endurgera",
15 | "select_all": "Velja allt",
16 | "show_hide": "Sýna/Fela",
17 | "undo": "Afturkalla",
18 | "zoom_in": "Stækka",
19 | "zoom_out": "Minnka"
20 | },
21 | "common": {
22 | "about": "Um hugbúnaðinn",
23 | "help": "Hjálp",
24 | "preferences": "Stillingar"
25 | },
26 | "confirm_quit": "Ertu viss um að þú viljir hætta?",
27 | "edit_menu": {
28 | "speech": "Tal",
29 | "speech_start_speaking": "Byrja tal",
30 | "speech_stop_speaking": "Hætta tali"
31 | },
32 | "file_menu": {
33 | "label": "Skrá"
34 | },
35 | "menu": {
36 | "hide": "Fela",
37 | "hide_others": "Fela aðra",
38 | "services": "Þjónustur",
39 | "unhide": "Birta"
40 | },
41 | "right_click_menu": {
42 | "add_to_dictionary": "Bæta við orðasafn",
43 | "copy_email": "Afrita tölvupóstfang",
44 | "copy_image": "Afrita mynd",
45 | "copy_image_url": "Afrita slóð myndar",
46 | "copy_link_url": "Afrita vistfang tengils",
47 | "save_image_as": "Vista mynd sem...",
48 | "save_image_as_error_description": "Myndina var ekki hægt að vista",
49 | "save_image_as_error_title": "Mistókst að vista mynd"
50 | },
51 | "view_menu": {
52 | "actual_size": "Raunstærð",
53 | "toggle_developer_tools": "Víxla forritunarverkfærum af/á",
54 | "toggle_full_screen": "Víxla fullum skjá af/á",
55 | "view": "Skoða"
56 | },
57 | "window_menu": {
58 | "bring_all_to_front": "Setja allt fremst",
59 | "label": "Gluggi",
60 | "zoom": "Stærð"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/i18n/strings/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Annulla",
4 | "close": "Chiudi",
5 | "close_brand": "Chiudi %(brand)s",
6 | "copy": "Copia",
7 | "cut": "Taglia",
8 | "delete": "Elimina",
9 | "edit": "Modifica",
10 | "minimise": "Riduci",
11 | "paste": "Incolla",
12 | "paste_match_style": "Incolla e adegua lo stile",
13 | "quit": "Esci",
14 | "redo": "Ripeti",
15 | "select_all": "Seleziona tutto",
16 | "show_hide": "Mostra/Nascondi",
17 | "undo": "Annulla",
18 | "zoom_in": "Ingrandisci",
19 | "zoom_out": "Rimpicciolisci"
20 | },
21 | "common": {
22 | "about": "Informazioni su",
23 | "brand_help": "Aiuto per %(brand)s",
24 | "help": "Aiuto",
25 | "preferences": "Preferenze"
26 | },
27 | "confirm_quit": "Vuoi veramente uscire?",
28 | "edit_menu": {
29 | "speech": "Dettatura",
30 | "speech_start_speaking": "Inizia a parlare",
31 | "speech_stop_speaking": "Smetti di parlare"
32 | },
33 | "file_menu": {
34 | "label": "File"
35 | },
36 | "menu": {
37 | "hide": "Nascondi",
38 | "hide_others": "Nascondi gli altri",
39 | "services": "Servizi",
40 | "unhide": "Mostra"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Aggiungi al dizionario",
44 | "copy_email": "Copia indirizzo email",
45 | "copy_image": "Copia immagine",
46 | "copy_image_url": "Copia indirizzo immagine",
47 | "copy_link_url": "Copia indirizzo collegamento",
48 | "save_image_as": "Salva immagine come...",
49 | "save_image_as_error_description": "Non è stato possibile salvare l'immagine",
50 | "save_image_as_error_title": "Salvataggio immagine fallito"
51 | },
52 | "view_menu": {
53 | "actual_size": "Dimensione effettiva",
54 | "toggle_developer_tools": "Attiva strumenti per sviluppatori",
55 | "toggle_full_screen": "Passa a schermo intero",
56 | "view": "Vedi"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Porta tutto in primo piano",
60 | "label": "Finestra",
61 | "zoom": "Ingrandisci"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "キャンセル",
4 | "close": "閉じる",
5 | "close_brand": "%(brand)sを閉じる",
6 | "copy": "コピー",
7 | "cut": "切り取り",
8 | "delete": "削除",
9 | "edit": "編集",
10 | "minimise": "最小化",
11 | "paste": "貼り付け",
12 | "paste_match_style": "スタイルを保持して貼り付け",
13 | "quit": "終了",
14 | "redo": "やり直す",
15 | "select_all": "全て選択",
16 | "show_hide": "表示/非表示",
17 | "undo": "取り消す",
18 | "zoom_in": "拡大",
19 | "zoom_out": "縮小"
20 | },
21 | "common": {
22 | "about": "概要",
23 | "help": "ヘルプ",
24 | "preferences": "環境設定"
25 | },
26 | "confirm_quit": "終了してよろしいですか?",
27 | "edit_menu": {
28 | "speech": "スピーチ",
29 | "speech_start_speaking": "録音を開始",
30 | "speech_stop_speaking": "録音を停止"
31 | },
32 | "file_menu": {
33 | "label": "ファイル"
34 | },
35 | "menu": {
36 | "hide": "非表示",
37 | "hide_others": "他を非表示",
38 | "services": "サービス",
39 | "unhide": "再表示"
40 | },
41 | "right_click_menu": {
42 | "add_to_dictionary": "辞書に追加",
43 | "copy_email": "メールアドレスをコピー",
44 | "copy_image": "画像をコピー",
45 | "copy_image_url": "画像のアドレスをコピー",
46 | "copy_link_url": "リンクのアドレスをコピー",
47 | "save_image_as": "画像を保存",
48 | "save_image_as_error_description": "画像の保存に失敗しました",
49 | "save_image_as_error_title": "画像の保存に失敗"
50 | },
51 | "view_menu": {
52 | "actual_size": "等倍",
53 | "toggle_developer_tools": "開発者ツールを切り替える",
54 | "toggle_full_screen": "全画面表示を切り替える",
55 | "view": "表示"
56 | },
57 | "window_menu": {
58 | "bring_all_to_front": "全てを前面に表示",
59 | "label": "ウィンドウ",
60 | "zoom": "ズーム"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/i18n/strings/ka.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "გაუქმება",
4 | "close": "დახურვა",
5 | "close_brand": "დახურვა %(brand)s",
6 | "copy": "კოპირება",
7 | "cut": "მოჭრა",
8 | "delete": "წაშალეთ",
9 | "edit": "რედაქტირება",
10 | "minimise": "შეამცირეთ",
11 | "paste": "პასტა",
12 | "paste_match_style": "ჩასვით და მატჩის სტილი",
13 | "quit": "თავი დაანებე",
14 | "redo": "რედო",
15 | "select_all": "აირჩიეთ ყველა",
16 | "show_hide": "ჩვენება/დამალვა",
17 | "undo": "გაუქმება",
18 | "zoom_in": "გაზარდოთ",
19 | "zoom_out": "გაფართოება"
20 | },
21 | "common": {
22 | "about": "შესახებ",
23 | "brand_help": "%(brand)sდახმარება",
24 | "help": "დახმარება",
25 | "preferences": "პრეფერენციები"
26 | },
27 | "confirm_quit": "დარწმუნებული ხართ, რომ გსურთ დატოვება?",
28 | "edit_menu": {
29 | "speech": "გამოსვლა",
30 | "speech_start_speaking": "დაიწყეთ საუბარი",
31 | "speech_stop_speaking": "შეწყვიტე ლაპარ"
32 | },
33 | "file_menu": {
34 | "label": "ფაილი"
35 | },
36 | "menu": {
37 | "hide": "დამალვა",
38 | "hide_others": "სხვების დამალვა",
39 | "services": "მომსახურება",
40 | "unhide": "გამოხატე"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "ლექსიკონში დამატება",
44 | "copy_email": "ელ. ფოსტის მისამართის",
45 | "copy_image": "სურათის დაკოპირება",
46 | "copy_image_url": "გამოსახულების მისამართის კოპირ",
47 | "copy_link_url": "ბმულის მისამართის კოპირება",
48 | "save_image_as": "შეინახეთ სურათი როგორც...",
49 | "save_image_as_error_description": "სურათის შენახვა ვერ შეძლო",
50 | "save_image_as_error_title": "სურათის შენახვა ვერ შეძლ"
51 | },
52 | "view_menu": {
53 | "actual_size": "რეალური ზომა",
54 | "toggle_developer_tools": "დეველოპერის ინსტრუმენტების",
55 | "toggle_full_screen": "სრული ეკრანის გადართვა",
56 | "view": "ნახვა"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "ყველაფერი წინ წამოიყვანეთ",
60 | "label": "ფანჯარა",
61 | "zoom": "გაზუსტება"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/lo.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "ຍົກເລີກ",
4 | "close": "ປິດ",
5 | "copy": "ສຳເນົາ",
6 | "cut": "ຕັດ",
7 | "delete": "ລຶບ",
8 | "edit": "ແກ້ໄຂ",
9 | "minimise": "ຫຍໍ້ນ້ອຍ",
10 | "paste": "ກັອບມາໃສ່",
11 | "paste_match_style": "ກັອບມາໃສ່ ແລະໃຫ້ສະຕາຍຕົງກັນ",
12 | "quit": "ຍົກເລີກ",
13 | "redo": "ລຶ້ມຄືນ",
14 | "select_all": "ເລືອກທັງໝົດ",
15 | "show_hide": "ສະແດງ/ເຊື່ອງ",
16 | "undo": "ຮື້ຄືນ",
17 | "zoom_in": "ຊູມເຂົ້າ",
18 | "zoom_out": "ຊູມອອກ"
19 | },
20 | "common": {
21 | "about": "ກ່ຽວກັບ",
22 | "help": "ຊ່ວຍເຫຼືອ",
23 | "preferences": "ການຕັ້ງຄ່າ"
24 | },
25 | "confirm_quit": "ທ່ານຕ້ອງການປິດແທ້ບໍ່?",
26 | "edit_menu": {
27 | "speech": "ຄຳກ່າວ",
28 | "speech_start_speaking": "ເລີ່ມສົນທະນາ",
29 | "speech_stop_speaking": "ເຊົາສົນທະນາ"
30 | },
31 | "file_menu": {
32 | "label": "ຟາຍ"
33 | },
34 | "menu": {
35 | "hide": "ເຊື່ອງ",
36 | "hide_others": "ເຊື່ອງອັນອື່ນ",
37 | "services": "ບໍລິການ",
38 | "unhide": "ໂຊຄືນ"
39 | },
40 | "right_click_menu": {
41 | "add_to_dictionary": "ເພີ່ມເຂົ້າໄປວັດຈະນານຸກົມ",
42 | "copy_email": "ສຳເນົາທີ່ຢູ່ເມວ",
43 | "copy_image": "ສຳເນົາຮູບ",
44 | "copy_image_url": "ສຳເນົາທີ່ຢູ່ຮູບພາບ",
45 | "copy_link_url": "ສຳເນົາທີ່ຢູ່ລິ້ງ",
46 | "save_image_as": "ບັນທຶກຮູບພາບເປັນ...",
47 | "save_image_as_error_description": "ຮູບພາບບໍ່ສາມາດບັດທຶກໄດ້",
48 | "save_image_as_error_title": "ການບັນທຶກຮູບພາບບໍ່ສຳເລັດ"
49 | },
50 | "view_menu": {
51 | "actual_size": "ຂະໜາດຕົວຈິງ",
52 | "toggle_developer_tools": "ສະຫຼັບໄປໜ້າເຄື່ອງມືພັດທະນາ",
53 | "toggle_full_screen": "ສະຫຼັບເຕັມຈໍ",
54 | "view": "ເບິ່ງ"
55 | },
56 | "window_menu": {
57 | "bring_all_to_front": "ເອົາທັງໝົດມາທາງໜ້າ",
58 | "label": "ປ່ອງຢ້ຽມ",
59 | "zoom": "ຊູມ"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/i18n/strings/lt.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Atšaukti",
4 | "close": "Uždaryti",
5 | "close_brand": "Uždaryti %(brand)s",
6 | "copy": "Kopijuoti",
7 | "cut": "Iškirpti",
8 | "delete": "Ištrinti",
9 | "edit": "Koreguoti",
10 | "minimise": "Sumažinti",
11 | "paste": "Įklijuoti",
12 | "paste_match_style": "Įklijuoti ir suderinti stilių",
13 | "quit": "Išeiti",
14 | "redo": "Sugrąžinti veiksmą",
15 | "select_all": "Pasirinkti visus",
16 | "show_hide": "Rodyti/Slėpti",
17 | "undo": "Atšaukti veiksmą",
18 | "zoom_in": "Priartinti",
19 | "zoom_out": "Atitolinti"
20 | },
21 | "common": {
22 | "about": "Apie",
23 | "help": "Pagalba",
24 | "preferences": "Nuostatos"
25 | },
26 | "confirm_quit": "Ar tikrai norite išeiti?",
27 | "edit_menu": {
28 | "speech": "Kalba",
29 | "speech_start_speaking": "Pradėti kalbėti",
30 | "speech_stop_speaking": "Nustoti kalbėti"
31 | },
32 | "file_menu": {
33 | "label": "Failas"
34 | },
35 | "menu": {
36 | "hide": "Slėpti",
37 | "hide_others": "Slėpti kitus",
38 | "services": "Paslaugos",
39 | "unhide": "Nebeslėpti"
40 | },
41 | "right_click_menu": {
42 | "add_to_dictionary": "Pridėti prie žodyno",
43 | "copy_email": "Kopijuoti el. pašto adresą",
44 | "copy_image": "Kopijuoti paveikslėlį",
45 | "copy_image_url": "Kopijuoti paveikslėlio adresą",
46 | "copy_link_url": "Kopijuoti nuorodos adresą",
47 | "save_image_as": "Įrašyti paveikslėlį kaip...",
48 | "save_image_as_error_description": "Paveikslėlio nepavyko išsaugoti",
49 | "save_image_as_error_title": "Nepavyko įrašyti paveikslėlio"
50 | },
51 | "view_menu": {
52 | "actual_size": "Tikrasis dydis",
53 | "toggle_developer_tools": "Perjungti kūrėjo įrankius",
54 | "toggle_full_screen": "Perjungti viso ekrano režimą",
55 | "view": "Žiūrėti"
56 | },
57 | "window_menu": {
58 | "bring_all_to_front": "Viską iškelti į priekį",
59 | "label": "Langas",
60 | "zoom": "Priartinti"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/i18n/strings/lv.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Atcelt",
4 | "close": "Aizvērt",
5 | "close_brand": "Aizvērt %(brand)s",
6 | "copy": "Kopēt",
7 | "cut": "Izgriezt",
8 | "delete": "Dzēst",
9 | "edit": "Labot",
10 | "minimise": "Samazināt",
11 | "paste": "Ielīmēt",
12 | "paste_match_style": "Ielīmēt un pielāgot stilu",
13 | "quit": "Iziet",
14 | "redo": "Atatsaukt",
15 | "select_all": "Atzīmēt visu",
16 | "show_hide": "Parādīt/paslēpt",
17 | "undo": "Atsaukt",
18 | "zoom_in": "Tuvināt",
19 | "zoom_out": "Tālināt"
20 | },
21 | "common": {
22 | "about": "Par",
23 | "brand_help": "%(brand)s palīdzība",
24 | "help": "Palīdzība",
25 | "preferences": "Iestatījumi"
26 | },
27 | "confirm_quit": "Vai tiešām iziet?",
28 | "edit_menu": {
29 | "speech": "Runa",
30 | "speech_start_speaking": "Uzsākt runāšanu",
31 | "speech_stop_speaking": "Pārtraukt runāšanu"
32 | },
33 | "file_menu": {
34 | "label": "Datne"
35 | },
36 | "menu": {
37 | "hide": "Paslēpt",
38 | "hide_others": "Paslēpt citus",
39 | "services": "Pakalpojumi",
40 | "unhide": "Rādīt"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Pievienot vārdnīcai",
44 | "copy_email": "Ievietot e-pasta adresi starpliktuvē",
45 | "copy_image": "Ievietot attēlu starpliktuvē",
46 | "copy_image_url": "Ievietot attēla adresi starpliktuvē",
47 | "copy_link_url": "Ievietot saites adresi starpliktuvē",
48 | "save_image_as": "Saglabāt attēlu kā...",
49 | "save_image_as_error_description": "Attēlu neizdevās saglabāt",
50 | "save_image_as_error_title": "Neizdevās saglabāt attēlu"
51 | },
52 | "view_menu": {
53 | "actual_size": "Īstais izmērs",
54 | "toggle_developer_tools": "Pārslēgt izstrādātāja rīkus",
55 | "toggle_full_screen": "Pārslēgt pilnekrānu",
56 | "view": "Skats"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Iznest visu priekšplānā",
60 | "label": "Logs",
61 | "zoom": "Tālummaiņa"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/mg_MG.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Hanafoana",
4 | "close": "Akatona",
5 | "close_brand": "Anakatona%(brand)s",
6 | "copy": "Dika Mitovy",
7 | "cut": "Tapaina",
8 | "delete": "Fafaina",
9 | "edit": "Anova",
10 | "minimise": "Manamaivana",
11 | "paste": "Koba",
12 | "paste_match_style": "Mametaka sy Mampifanandrify ny fomba",
13 | "quit": "Mialà",
14 | "redo": "Averina atao",
15 | "select_all": "Isafidy ny rehetra",
16 | "show_hide": "Aneho/Anafina",
17 | "undo": "Ravao",
18 | "zoom_in": "Angedao",
19 | "zoom_out": "Hahelezo"
20 | },
21 | "common": {
22 | "about": "Mombamomba",
23 | "brand_help": "%(marques)Fanampiana",
24 | "help": "Fanampiana",
25 | "preferences": "Safidy manokana"
26 | },
27 | "confirm_quit": "Azo Antoka ve fa tena hiala ianao",
28 | "edit_menu": {
29 | "speech": "Fitenenana",
30 | "speech_start_speaking": "Atomboy ny resaka/Manomboha fitenenena",
31 | "speech_stop_speaking": "Atsaharo ny fitenenana"
32 | },
33 | "file_menu": {
34 | "label": "Manapetraka/apetrao"
35 | },
36 | "menu": {
37 | "hide": "Afeno",
38 | "hide_others": "Afeno ny hafa",
39 | "services": "Tolotra",
40 | "unhide": "Asehoy"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Ampio ao amin'ny rakibolana",
44 | "copy_email": "Adikao ny adiresy imailaka",
45 | "copy_image": "Andika ny sary",
46 | "copy_image_url": "Adikao ny adiresin'ny sary",
47 | "copy_link_url": "Adikao ny adiresy rohy",
48 | "save_image_as": "Hitahiry ny sary ho",
49 | "save_image_as_error_description": "Tsy voatahiry ilay sary",
50 | "save_image_as_error_title": "Tsy nahahomby ny fitahirizana an'ilay sary"
51 | },
52 | "view_menu": {
53 | "actual_size": "Habe Ankehitriny",
54 | "toggle_developer_tools": "Amadika fitaovana fampandrosoana",
55 | "toggle_full_screen": "Hamadika amin'ny efijery feno",
56 | "view": "Hijery"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Ataovy aloha ny zava-drehetra",
60 | "label": "Varavarankely",
61 | "zoom": "Anakaiky fahitana"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/nb_NO.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Avbryt",
4 | "close": "Lukk",
5 | "close_brand": "Avslutt %(brand)s",
6 | "copy": "Kopier",
7 | "cut": "Klipp",
8 | "delete": "Slett",
9 | "edit": "Rediger",
10 | "minimise": "Minimere",
11 | "paste": "Lim inn",
12 | "paste_match_style": "Lim inn og match stil",
13 | "quit": "Avslutt",
14 | "redo": "Gjør om",
15 | "select_all": "Velg alle",
16 | "show_hide": "Vis/Skjul",
17 | "undo": "Angre",
18 | "zoom_in": "Zoom inn",
19 | "zoom_out": "Zoom ut"
20 | },
21 | "common": {
22 | "about": "Om",
23 | "brand_help": "%(brand)s Hjelp",
24 | "help": "Hjelp",
25 | "no": "Nei",
26 | "preferences": "Brukervalg",
27 | "yes": "Ja"
28 | },
29 | "confirm_quit": "Er du sikker på at du vil slutte?",
30 | "edit_menu": {
31 | "speech": "Tale",
32 | "speech_start_speaking": "Begynn å snakke",
33 | "speech_stop_speaking": "Slutt å snakke"
34 | },
35 | "file_menu": {
36 | "label": "Fil"
37 | },
38 | "menu": {
39 | "hide": "Skjul",
40 | "hide_others": "Skjul andre",
41 | "services": "Tjenester",
42 | "unhide": "Slutt å skjule"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Legg til i ordbok",
46 | "copy_email": "Kopier e-postadressen",
47 | "copy_image": "Kopier bildet",
48 | "copy_image_url": "Kopier bildeadresse",
49 | "copy_link_url": "Kopier link adresse",
50 | "save_image_as": "Lagre bildet som...",
51 | "save_image_as_error_description": "Bildet kunne ikke lagres",
52 | "save_image_as_error_title": "Kunne ikke lagre bildet"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Tøm data og last inn på nytt?",
57 | "backend_changed_detail": "Kan ikke få tilgang til hemmeligheten fra systemnøkkelringen, den ser ut til å ha blitt endret.",
58 | "backend_changed_title": "Kunne ikke laste inn databasen",
59 | "backend_no_encryption": "Systemet ditt har en støttet nøkkelring, men kryptering er ikke tilgjengelig.",
60 | "backend_no_encryption_detail": "Electron har oppdaget at kryptering ikke er tilgjengelig på nøkkelringen %(backend)s din. Forsikre deg om at du har nøkkelringen installert. Hvis du har nøkkelringen installert, vennligst start på nytt og prøv igjen. Eventuelt kan du tillate %(brand)s å bruke en svakere form for kryptering.",
61 | "backend_no_encryption_title": "Ingen støtte for kryptering",
62 | "unsupported_keyring": "Systemet ditt har en nøkkelring som ikke støttes, noe som betyr at databasen ikke kan åpnes.",
63 | "unsupported_keyring_detail": "Electrons nøkkelringdeteksjon fant ikke en støttet backend. Du kan prøve å konfigurere backend manuelt ved å starte %(brand)s med et kommandolinjeargument, en engangsoperasjon. Se%(link)s.",
64 | "unsupported_keyring_title": "Systemet støttes ikke",
65 | "unsupported_keyring_use_basic_text": "Bruk svakere kryptering",
66 | "unsupported_keyring_use_plaintext": "Ikke bruk kryptering"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Faktisk størrelse",
71 | "toggle_developer_tools": "Veksle Utvikleralternativer",
72 | "toggle_full_screen": "Veksle Fullskjerm",
73 | "view": "Vis"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Flytt Alt Frem",
77 | "label": "Vindu",
78 | "zoom": "Forstørr"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/nl.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Annuleren",
4 | "close": "Sluiten",
5 | "close_brand": "Sluit %(brand)s",
6 | "copy": "Kopiëren",
7 | "cut": "Knippen",
8 | "delete": "Verwijderen",
9 | "edit": "Bewerken",
10 | "minimise": "Minimaliseren",
11 | "paste": "Plakken",
12 | "paste_match_style": "Plakken zonder stijl",
13 | "quit": "Sluiten",
14 | "redo": "Opnieuw doen",
15 | "select_all": "Alles selecteren",
16 | "show_hide": "Tonen/Verbergen",
17 | "undo": "Ongedaan maken",
18 | "zoom_in": "Inzoomen",
19 | "zoom_out": "Uitzoomen"
20 | },
21 | "common": {
22 | "about": "Over",
23 | "brand_help": "%(brand)s Hulp",
24 | "help": "Hulp",
25 | "preferences": "Voorkeuren"
26 | },
27 | "confirm_quit": "Weet u zeker dat u wilt stoppen?",
28 | "edit_menu": {
29 | "speech": "Spraak",
30 | "speech_start_speaking": "Begin met praten",
31 | "speech_stop_speaking": "Stop met praten"
32 | },
33 | "file_menu": {
34 | "label": "Bestand"
35 | },
36 | "menu": {
37 | "hide": "Verbergen",
38 | "hide_others": "Anderen verbergen",
39 | "services": "Diensten",
40 | "unhide": "Weer laten zien"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Aan woordenboek toevoegen",
44 | "copy_email": "E-mailadres kopiëren",
45 | "copy_image": "Afbeelding kopiëren",
46 | "copy_image_url": "Kopieer afbeeldingsadres",
47 | "copy_link_url": "Link kopiëren",
48 | "save_image_as": "Afbeelding opslaan als...",
49 | "save_image_as_error_description": "De afbeelding opslaan is mislukt",
50 | "save_image_as_error_title": "Afbeelding opslaan is mislukt"
51 | },
52 | "view_menu": {
53 | "actual_size": "Werkelijke grootte",
54 | "toggle_developer_tools": "Developer Tools wisselen",
55 | "toggle_full_screen": "Volledig scherm wisselen",
56 | "view": "Bekijken"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Alles naar voren brengen",
60 | "label": "Venster",
61 | "zoom": "Zoom"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/pl.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Anuluj",
4 | "close": "Zamknij",
5 | "close_brand": "Zamknij %(brand)s",
6 | "copy": "Kopiuj",
7 | "cut": "Wytnij",
8 | "delete": "Usuń",
9 | "edit": "Edytuj",
10 | "minimise": "Minimalizuj",
11 | "paste": "Wklej",
12 | "paste_match_style": "Wklej i dopasuj styl",
13 | "quit": "Zamknij",
14 | "redo": "Ponów",
15 | "select_all": "Zaznacz wszystko",
16 | "show_hide": "Pokaż/Ukryj",
17 | "undo": "Cofnij",
18 | "zoom_in": "Powiększ",
19 | "zoom_out": "Pomniejsz"
20 | },
21 | "common": {
22 | "about": "Informacje",
23 | "brand_help": "Pomoc %(brand)s",
24 | "help": "Pomoc",
25 | "no": "Nie",
26 | "preferences": "Preferencje",
27 | "yes": "Tak"
28 | },
29 | "confirm_quit": "Czy na pewno chcesz zamknąć?",
30 | "edit_menu": {
31 | "speech": "Mowa",
32 | "speech_start_speaking": "Zacznij mówić",
33 | "speech_stop_speaking": "Przestań mówić"
34 | },
35 | "file_menu": {
36 | "label": "Plik"
37 | },
38 | "menu": {
39 | "hide": "Ukryj",
40 | "hide_others": "Ukryj inne",
41 | "services": "Usługi",
42 | "unhide": "Odkryj"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Dodaj do słownika",
46 | "copy_email": "Kopiuj adres e-mail",
47 | "copy_image": "Kopiuj obraz",
48 | "copy_image_url": "Kopiuj adres obrazu",
49 | "copy_link_url": "Kopiuj adres odnośnika",
50 | "save_image_as": "Zapisz obraz jako...",
51 | "save_image_as_error_description": "Obraz nie został zapisany",
52 | "save_image_as_error_title": "Nie udało się zapisać obrazu"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Wyczyścić dane i przeładować?",
57 | "backend_changed_detail": "Nie można uzyskać dostępu do sekretnego magazynu, wygląda na to, że uległ zmianie.",
58 | "backend_changed_title": "Nie udało się załadować bazy danych",
59 | "unsupported_keyring": "System zawiera niewspierany keyring, nie można otworzyć bazy danych.",
60 | "unsupported_keyring_detail": "Wykrywanie keyringu Electron nie znalazł wspieranego backendu. Możesz spróbować ręcznie ustawić backed, uruchamiając %(brand)s za pomocą wiesza poleceń. Zobacz %(link)s.",
61 | "unsupported_keyring_title": "System niewspierany"
62 | }
63 | },
64 | "view_menu": {
65 | "actual_size": "Rozmiar rzeczywisty",
66 | "toggle_developer_tools": "Przełącz narzędzia deweloperskie",
67 | "toggle_full_screen": "Przełącz pełny ekran",
68 | "view": "Wyświetl"
69 | },
70 | "window_menu": {
71 | "bring_all_to_front": "Wyciągnij wszystko do przodu",
72 | "label": "Okno",
73 | "zoom": "Powiększenie"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/i18n/strings/pt.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Cancelar",
4 | "close": "Fechar",
5 | "close_brand": "Fecha %(brand)s",
6 | "copy": "Copiar",
7 | "cut": "Cortar",
8 | "delete": "Apagar",
9 | "edit": "Editar",
10 | "minimise": "Minimizar",
11 | "paste": "Colar",
12 | "paste_match_style": "Colar e combinar o estilo",
13 | "quit": "Desistir",
14 | "redo": "Refazer",
15 | "select_all": "Selecionar tudo",
16 | "show_hide": "Mostrar/ocultar",
17 | "undo": "Desfazer",
18 | "zoom_in": "Ampliar",
19 | "zoom_out": "Reduzir"
20 | },
21 | "common": {
22 | "about": "Sobre",
23 | "brand_help": "%(brand)s Ajuda",
24 | "help": "Ajuda",
25 | "preferences": "Preferências"
26 | },
27 | "confirm_quit": "Tens a certeza de que queres desistir?",
28 | "edit_menu": {
29 | "speech": "Discurso",
30 | "speech_start_speaking": "Começa a falar",
31 | "speech_stop_speaking": "Pára de falar"
32 | },
33 | "file_menu": {
34 | "label": "Ficheiro"
35 | },
36 | "menu": {
37 | "hide": "Ocultar",
38 | "hide_others": "Ocultar Outros",
39 | "services": "Serviços",
40 | "unhide": "Mostrar"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Adicionar ao dicionário",
44 | "copy_email": "Copiar endereço de e-mail",
45 | "copy_image": "Copiar imagem",
46 | "copy_image_url": "Copiar endereço da imagem",
47 | "copy_link_url": "Copiar endereço do link",
48 | "save_image_as": "Salvar imagem como...",
49 | "save_image_as_error_description": "A imagem não foi salva",
50 | "save_image_as_error_title": "Falha ao salvar a imagem"
51 | },
52 | "view_menu": {
53 | "actual_size": "Tamanho original",
54 | "toggle_developer_tools": "Alternar ferramentas de desenvolvedor",
55 | "toggle_full_screen": "Alternar ecrã inteiro",
56 | "view": "Ver"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Traz tudo para a frente",
60 | "label": "Janela",
61 | "zoom": "Ampliação"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/pt_BR.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Cancelar",
4 | "close": "Fechar",
5 | "close_brand": "Fechar %(brand)s",
6 | "copy": "Copiar",
7 | "cut": "Cortar",
8 | "delete": "Excluir",
9 | "edit": "Editar",
10 | "minimise": "Minimizar",
11 | "paste": "Colar",
12 | "paste_match_style": "Colar e Adequar Estilo",
13 | "quit": "Sair",
14 | "redo": "Refazer",
15 | "select_all": "Selecionar Todas",
16 | "show_hide": "Mostrar/Esconder",
17 | "undo": "Desfazer",
18 | "zoom_in": "Dar Zoom In",
19 | "zoom_out": "Dar Zoom Out"
20 | },
21 | "common": {
22 | "about": "Sobre",
23 | "brand_help": "%(brand)s Ajuda",
24 | "help": "Ajuda",
25 | "no": "Não",
26 | "preferences": "Preferências",
27 | "yes": "Sim"
28 | },
29 | "confirm_quit": "Você tem certeza que você quer sair?",
30 | "edit_menu": {
31 | "speech": "Fala",
32 | "speech_start_speaking": "Começar a Falar",
33 | "speech_stop_speaking": "Parar de Falar"
34 | },
35 | "file_menu": {
36 | "label": "Arquivo"
37 | },
38 | "menu": {
39 | "hide": "Esconder",
40 | "hide_others": "Esconder Outras(os)",
41 | "services": "Serviços",
42 | "unhide": "Desesconder"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Adicionar a dicionário",
46 | "copy_email": "Copiar endereço de email",
47 | "copy_image": "Copiar imagem",
48 | "copy_image_url": "Copiar endereço de imagem",
49 | "copy_link_url": "Copiar endereço de link",
50 | "save_image_as": "Salvar imagem como...",
51 | "save_image_as_error_description": "A imagem falhou para salvar",
52 | "save_image_as_error_title": "Falha para salvar imagem"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Limpar dados e recarregar?",
57 | "backend_changed_detail": "Não foi possível acessar o segredo no cofre do sistema, parece que ele foi alterado.",
58 | "backend_changed_title": "Falha ao carregar o banco de dados",
59 | "backend_no_encryption": "Seu sistema tem um cofre compatível, mas a criptografia não está disponível.",
60 | "backend_no_encryption_detail": "O Electron detetou que a encriptação não está disponível no seu cofre %(backend)s. Certifique-se de que tem o cofre instalado. Se tiver o cofre instalado, reinicie e tente novamente. Opcionalmente, você pode permitir que %(brand)s use uma forma mais fraca de criptografia.",
61 | "backend_no_encryption_title": "Sem suporte para criptografia",
62 | "unsupported_keyring": "Seu sistema possui um cofre não compatível, o que impede a abertura do banco de dados.",
63 | "unsupported_keyring_detail": "A detecção de cofre do Electron não encontrou um backend compatível. Você pode tentar configurar manualmente o backend iniciando %(brand)s com um argumento de linha de comando, uma operação única. Consulte %(link)s.",
64 | "unsupported_keyring_title": "Sistema não suportado",
65 | "unsupported_keyring_use_basic_text": "Use criptografia mais fraca",
66 | "unsupported_keyring_use_plaintext": "Não usar criptografia"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Tamanho de Verdade",
71 | "toggle_developer_tools": "Ativar/Desativar Ferramentas de Desenvolvimento",
72 | "toggle_full_screen": "Pôr em/Tirar de Tela Cheia",
73 | "view": "Ver"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Trazer Todas Para Frente",
77 | "label": "Janela",
78 | "zoom": "Zoom"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Отмена",
4 | "close": "Закрыть",
5 | "close_brand": "Закрыть %(brand)s",
6 | "copy": "Копировать",
7 | "cut": "Вырезать",
8 | "delete": "Удалить",
9 | "edit": "Изменить",
10 | "minimise": "Свернуть",
11 | "paste": "Вставить",
12 | "paste_match_style": "Вставить с тем же стилем",
13 | "quit": "Выйти",
14 | "redo": "Повторить",
15 | "select_all": "Выбрать все",
16 | "show_hide": "Показать/скрыть",
17 | "undo": "Отменить",
18 | "zoom_in": "Увеличить",
19 | "zoom_out": "Уменьшить"
20 | },
21 | "common": {
22 | "about": "О программе",
23 | "brand_help": "Помощь %(brand)s",
24 | "help": "Помощь",
25 | "no": "Нет",
26 | "preferences": "Предпочтения",
27 | "yes": "Да"
28 | },
29 | "confirm_quit": "Вы уверены, что хотите выйти?",
30 | "edit_menu": {
31 | "speech": "Речь",
32 | "speech_start_speaking": "Говорите",
33 | "speech_stop_speaking": "Перестаньте говорить"
34 | },
35 | "file_menu": {
36 | "label": "Файл"
37 | },
38 | "menu": {
39 | "hide": "Скрыть",
40 | "hide_others": "Скрыть прочие",
41 | "services": "Службы",
42 | "unhide": "Показать"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Добавить в словарь",
46 | "copy_email": "Копировать адрес почты",
47 | "copy_image": "Копировать изображение",
48 | "copy_image_url": "Копировать адрес изображения",
49 | "copy_link_url": "Копировать ссылку",
50 | "save_image_as": "Сохранить изображение как...",
51 | "save_image_as_error_description": "Не удалось сохранить изображение",
52 | "save_image_as_error_title": "Не удалось сохранить изображение"
53 | },
54 | "view_menu": {
55 | "actual_size": "Фактический размер",
56 | "toggle_developer_tools": "Переключить инструменты разработчика",
57 | "toggle_full_screen": "Переключить полноэкранный режим",
58 | "view": "Просмотр"
59 | },
60 | "window_menu": {
61 | "bring_all_to_front": "Вынести всё вперёд",
62 | "label": "Окно",
63 | "zoom": "Масштаб"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/i18n/strings/sk.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Zrušiť",
4 | "close": "Zavrieť",
5 | "close_brand": "Zatvoriť %(brand)s",
6 | "copy": "Kopírovať",
7 | "cut": "Vystrihnúť",
8 | "delete": "Odstrániť",
9 | "edit": "Upraviť",
10 | "minimise": "Minimalizovať",
11 | "paste": "Vložiť",
12 | "paste_match_style": "Vložiť a prispôsobiť štýl",
13 | "quit": "Ukončiť",
14 | "redo": "Opakovať",
15 | "select_all": "Vybrať všetko",
16 | "show_hide": "Zobraziť/Skryť",
17 | "undo": "Späť",
18 | "zoom_in": "Priblížiť",
19 | "zoom_out": "Oddialiť"
20 | },
21 | "common": {
22 | "about": "Informácie",
23 | "brand_help": "%(brand)s Pomoc",
24 | "help": "Pomocník",
25 | "no": "Nie",
26 | "preferences": "Predvoľby",
27 | "yes": "Áno"
28 | },
29 | "confirm_quit": "Naozaj chcete zavrieť aplikáciu?",
30 | "edit_menu": {
31 | "speech": "Reč",
32 | "speech_start_speaking": "Spustiť nahrávanie hlasu",
33 | "speech_stop_speaking": "Zastaviť nahrávanie hlasu"
34 | },
35 | "file_menu": {
36 | "label": "Súbor"
37 | },
38 | "menu": {
39 | "hide": "Skryť",
40 | "hide_others": "Skryť ostatné",
41 | "services": "Služby",
42 | "unhide": "Odkryť"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Pridať do slovníka",
46 | "copy_email": "Kopírovať e-mailovú adresu",
47 | "copy_image": "Kopírovať obrázok",
48 | "copy_image_url": "Kopírovať adresu obrázka",
49 | "copy_link_url": "Kopírovať adresu odkazu",
50 | "save_image_as": "Uložiť obrázok ako...",
51 | "save_image_as_error_description": "Obrázok sa nepodarilo uložiť",
52 | "save_image_as_error_title": "Chyba pri ukladaní obrázka"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Vymazať údaje a znova načítať?",
57 | "backend_changed_detail": "Nepodarilo sa získať prístup k tajnému kľúču zo systémového zväzku kľúčov, zdá sa, že sa zmenil.",
58 | "backend_changed_title": "Nepodarilo sa načítať databázu",
59 | "backend_no_encryption": "Váš systém má podporovaný zväzok kľúčov, ale šifrovanie nie je k dispozícii.",
60 | "backend_no_encryption_detail": "Electron zistil, že šifrovanie nie je k dispozícii na vašom zväzku kľúčov %(backend)s. Uistite sa, že máte nainštalovaný zväzok kľúčov. Ak máte zväzok kľúčov nainštalovaný, reštartujte počítač a skúste to znova. Voliteľne môžete povoliť aplikácii %(brand)s používať slabšiu formu šifrovania.",
61 | "backend_no_encryption_title": "Žiadna podpora šifrovania",
62 | "unsupported_keyring": "Váš systém má nepodporovaný zväzok kľúčov, čo znamená, že databázu nemožno otvoriť.",
63 | "unsupported_keyring_detail": "Detekcia zväzku kľúčov aplikácie Electron nenašla podporovaný backend. Môžete sa pokúsiť manuálne nastaviť backend spustením aplikácie %(brand)s s argumentom príkazového riadka, je to jednorazová operácia. Pozrite si %(link)s .",
64 | "unsupported_keyring_title": "Systém nie je podporovaný",
65 | "unsupported_keyring_use_basic_text": "Použiť slabšie šifrovanie",
66 | "unsupported_keyring_use_plaintext": "Nepoužiť žiadne šifrovanie"
67 | }
68 | },
69 | "view_menu": {
70 | "actual_size": "Aktuálna veľkosť",
71 | "toggle_developer_tools": "Nástroje pre vývojárov",
72 | "toggle_full_screen": "Celá obrazovka",
73 | "view": "Zobraziť"
74 | },
75 | "window_menu": {
76 | "bring_all_to_front": "Preniesť všetky do popredia",
77 | "label": "Okno",
78 | "zoom": "Lupa"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/i18n/strings/sq.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Anuloje",
4 | "close": "Mbylle",
5 | "copy": "Kopjoje",
6 | "delete": "Fshije",
7 | "edit": "Përpuno"
8 | },
9 | "common": {
10 | "about": "Mbi",
11 | "help": "Ndihmë",
12 | "preferences": "Parapëlqime"
13 | },
14 | "view_menu": {
15 | "view": "Shihni"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/i18n/strings/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Avbryt",
4 | "close": "Stäng",
5 | "close_brand": "Stäng %(brand)s",
6 | "copy": "Kopiera",
7 | "cut": "Klipp ut",
8 | "delete": "Radera",
9 | "edit": "Ändra",
10 | "minimise": "Minimera",
11 | "paste": "Klistra in",
12 | "paste_match_style": "Klistra in och matcha stilen",
13 | "quit": "Avsluta",
14 | "redo": "Gör om",
15 | "select_all": "Markera allt",
16 | "show_hide": "Visa/dölj",
17 | "undo": "Ångra",
18 | "zoom_in": "Zooma in",
19 | "zoom_out": "Zooma ut"
20 | },
21 | "common": {
22 | "about": "Om",
23 | "brand_help": "%(brand)s-hjälp",
24 | "help": "Hjälp",
25 | "no": "Nej",
26 | "preferences": "Inställningar",
27 | "yes": "Ja"
28 | },
29 | "confirm_quit": "Är du säker att du vill avsluta?",
30 | "edit_menu": {
31 | "speech": "Tal",
32 | "speech_start_speaking": "Börja tala",
33 | "speech_stop_speaking": "Sluta tala"
34 | },
35 | "file_menu": {
36 | "label": "Arkiv"
37 | },
38 | "menu": {
39 | "hide": "Göm",
40 | "hide_others": "Göm övriga",
41 | "services": "Tjänster",
42 | "unhide": "Sluta gömma"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Lägg till i ordlistan",
46 | "copy_email": "Kopiera e-postadress",
47 | "copy_image": "Kopiera bild",
48 | "copy_image_url": "Kopiera bildadress",
49 | "copy_link_url": "Kopiera länkadress",
50 | "save_image_as": "Spara bild som…",
51 | "save_image_as_error_description": "Bilden sparades inte",
52 | "save_image_as_error_title": "Misslyckades med att spara bilden"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Rensa data och ladda om?",
57 | "backend_changed_detail": "Kunde inte komma åt hemligheten från systemnyckelringen, det verkar ha ändrats.",
58 | "backend_changed_title": "Misslyckades att ladda databasen",
59 | "unsupported_keyring": "Ditt system har en nyckelring som inte stöds, vilket innebär att databasen inte kan öppnas."
60 | }
61 | },
62 | "view_menu": {
63 | "actual_size": "Verklig storlek",
64 | "toggle_developer_tools": "Växla utvecklarverktyg",
65 | "toggle_full_screen": "Växla helskärm",
66 | "view": "Visa"
67 | },
68 | "window_menu": {
69 | "bring_all_to_front": "Lägg alla överst",
70 | "label": "Fönster",
71 | "zoom": "Zooma"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/i18n/strings/tr.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "İptal",
4 | "close": "Kapat",
5 | "close_brand": "Kapat %(brand)s",
6 | "copy": "Kopyala",
7 | "cut": "Kes",
8 | "delete": "Sil",
9 | "edit": "Düzenle",
10 | "minimise": "Küçült",
11 | "paste": "Yapıştır",
12 | "paste_match_style": "Stili Yapıştır ve Eşleştir",
13 | "quit": "Çık",
14 | "redo": "Yeniden yap",
15 | "select_all": "Tümünü seç",
16 | "show_hide": "Göster/Gizle",
17 | "undo": "Geri al",
18 | "zoom_in": "Yakınlaştır",
19 | "zoom_out": "Uzaklaştır"
20 | },
21 | "common": {
22 | "about": "Hakkında",
23 | "brand_help": "%(brand)s Yardım",
24 | "help": "Yardım",
25 | "preferences": "Tercihler"
26 | },
27 | "confirm_quit": "Çıkmak istediğinizden emin misiniz?",
28 | "edit_menu": {
29 | "speech": "Konuşma",
30 | "speech_start_speaking": "Konuşmaya başla",
31 | "speech_stop_speaking": "Konuşmayı durdur"
32 | },
33 | "file_menu": {
34 | "label": "Dosya"
35 | },
36 | "menu": {
37 | "hide": "Gizle",
38 | "hide_others": "Diğerlerini gizle",
39 | "services": "Hizmetler",
40 | "unhide": "Göster"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Sözlüğe ekle",
44 | "copy_email": "E-posta adresini kopyala",
45 | "copy_image": "Resmi kopyala",
46 | "copy_image_url": "Görsel adresini kopyala",
47 | "copy_link_url": "Bağlantılı adresi kopyala",
48 | "save_image_as": "Resmi farklı kaydet...",
49 | "save_image_as_error_description": "Görüntü kaydedilemedi",
50 | "save_image_as_error_title": "Resim kaydedilemedi"
51 | },
52 | "view_menu": {
53 | "actual_size": "Gerçek boyut",
54 | "toggle_developer_tools": "Geliştirici araçları",
55 | "toggle_full_screen": "Tam ekran",
56 | "view": "Görüntüle"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Hepsini öne getir",
60 | "label": "Pencere",
61 | "zoom": "Yaklaştır"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/uk.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Скасувати",
4 | "close": "Закрити",
5 | "close_brand": "Закрити %(brand)s",
6 | "copy": "Скопіювати",
7 | "cut": "Вирізати",
8 | "delete": "Видалити",
9 | "edit": "Змінити",
10 | "minimise": "Згорнути",
11 | "paste": "Вставити",
12 | "paste_match_style": "Вставити з таким же стилем",
13 | "quit": "Вийти",
14 | "redo": "Повторити дію",
15 | "select_all": "Вибрати все",
16 | "show_hide": "Показати/Сховати",
17 | "undo": "Скасувати дію",
18 | "zoom_in": "Збільшити",
19 | "zoom_out": "Зменшити"
20 | },
21 | "common": {
22 | "about": "Про застосунок",
23 | "brand_help": "Довідка %(brand)s",
24 | "help": "Довідка",
25 | "no": "Ні",
26 | "preferences": "Параметри",
27 | "yes": "Так"
28 | },
29 | "confirm_quit": "Ви впевнені, що хочете вийти?",
30 | "edit_menu": {
31 | "speech": "Мовлення",
32 | "speech_start_speaking": "Почати говорити",
33 | "speech_stop_speaking": "Припинити говорити"
34 | },
35 | "file_menu": {
36 | "label": "Файл"
37 | },
38 | "menu": {
39 | "hide": "Сховати",
40 | "hide_others": "Сховати інші",
41 | "services": "Служби",
42 | "unhide": "Показати"
43 | },
44 | "right_click_menu": {
45 | "add_to_dictionary": "Додати до словника",
46 | "copy_email": "Копіювати адресу е-пошти",
47 | "copy_image": "Копіювати зображення",
48 | "copy_image_url": "Копіювати адресу зображення",
49 | "copy_link_url": "Копіювати адресу посилання",
50 | "save_image_as": "Зберегти зображення як...",
51 | "save_image_as_error_description": "Не вдалося зберегти зображення",
52 | "save_image_as_error_title": "Не вдалося зберегти зображення"
53 | },
54 | "store": {
55 | "error": {
56 | "backend_changed": "Очистити дані та перезавантажити?",
57 | "backend_changed_detail": "Не вдається отримати доступ до таємного ключа з системного набору ключів, видається, він змінився.",
58 | "backend_changed_title": "Не вдалося завантажити базу даних",
59 | "unsupported_keyring": "Ваша система має непідтримуваний набір ключів. Це означає, що базу даних неможливо відкрити.",
60 | "unsupported_keyring_detail": "Electron не виявив підтримуваного бекенда для роботи зі сховищем паролів. Ви можете вручну налаштувати його, запустивши %(brand)s з відповідним аргументом у командному рядку. Цю дію потрібно виконати лише один раз. Докладніше – %(link)s.",
61 | "unsupported_keyring_title": "Система не підтримується"
62 | }
63 | },
64 | "view_menu": {
65 | "actual_size": "Фактичний розмір",
66 | "toggle_developer_tools": "Перемкнути інструменти розробника",
67 | "toggle_full_screen": "Перемкнути повноекранний режим",
68 | "view": "Перегляд"
69 | },
70 | "window_menu": {
71 | "bring_all_to_front": "Винести все вперед",
72 | "label": "Вікно",
73 | "zoom": "Масштаб"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/i18n/strings/vi.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "Huỷ bỏ",
4 | "close": "Đóng",
5 | "close_brand": "Đóng %(brand)s",
6 | "copy": "Sao chép",
7 | "cut": "Cắt",
8 | "delete": "Xoá",
9 | "edit": "Sửa",
10 | "minimise": "Thu nhỏ",
11 | "paste": "Dán",
12 | "paste_match_style": "Dán và khớp kiểu",
13 | "quit": "Thoát",
14 | "redo": "Làm lại",
15 | "select_all": "Chọn tất cả",
16 | "show_hide": "Hiện/Ẩn",
17 | "undo": "Hoàn tác",
18 | "zoom_in": "Phóng to",
19 | "zoom_out": "Thu nhỏ"
20 | },
21 | "common": {
22 | "about": "Giới thiệu",
23 | "brand_help": "Hỗ trợ %(brand)s",
24 | "help": "Hỗ trợ",
25 | "preferences": "Tùy chọn"
26 | },
27 | "confirm_quit": "Bạn có chắc chắn muốn thoát?",
28 | "edit_menu": {
29 | "speech": "Đọc màn hình",
30 | "speech_start_speaking": "Bắt đầu nói",
31 | "speech_stop_speaking": "Dừng nói"
32 | },
33 | "file_menu": {
34 | "label": "Tệp"
35 | },
36 | "menu": {
37 | "hide": "Ẩn",
38 | "hide_others": "Ẩn cái khác",
39 | "services": "Dịch vụ",
40 | "unhide": "Bỏ ẩn"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "Thêm vào từ điển",
44 | "copy_email": "Sao chép địa chỉ email",
45 | "copy_image": "Sao chép ảnh",
46 | "copy_image_url": "Sao chép địa chỉ ảnh",
47 | "copy_link_url": "Sao chép địa chỉ liên kết",
48 | "save_image_as": "Lưu ảnh…",
49 | "save_image_as_error_description": "Ảnh không lưu được",
50 | "save_image_as_error_title": "Không lưu được ảnh"
51 | },
52 | "view_menu": {
53 | "actual_size": "Kích thước thực",
54 | "toggle_developer_tools": "Công cụ phát triển",
55 | "toggle_full_screen": "Toàn màn hình",
56 | "view": "Xem"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "Đưa tất cả lên trước",
60 | "label": "Cửa sổ",
61 | "zoom": "Thu phóng"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/zh_Hans.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "取消",
4 | "close": "关闭",
5 | "close_brand": "关闭 %(brand)s",
6 | "copy": "复制",
7 | "cut": "剪切",
8 | "delete": "删除",
9 | "edit": "编辑",
10 | "minimise": "最小化",
11 | "paste": "粘贴",
12 | "paste_match_style": "粘贴并匹配样式",
13 | "quit": "退出",
14 | "redo": "重做",
15 | "select_all": "选中全部",
16 | "show_hide": "显示/隐藏",
17 | "undo": "撤销",
18 | "zoom_in": "放大",
19 | "zoom_out": "缩小"
20 | },
21 | "common": {
22 | "about": "关于",
23 | "brand_help": "%(brand)s帮助",
24 | "help": "帮助",
25 | "preferences": "偏好"
26 | },
27 | "confirm_quit": "你确定要退出吗?",
28 | "edit_menu": {
29 | "speech": "讲话",
30 | "speech_start_speaking": "开始讲话",
31 | "speech_stop_speaking": "停止讲话"
32 | },
33 | "file_menu": {
34 | "label": "文件"
35 | },
36 | "menu": {
37 | "hide": "隐藏",
38 | "hide_others": "隐藏其他",
39 | "services": "服务",
40 | "unhide": "显示"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "添加到字典",
44 | "copy_email": "复制邮箱地址",
45 | "copy_image": "复制图片",
46 | "copy_image_url": "复制图片地址",
47 | "copy_link_url": "复制链接地址",
48 | "save_image_as": "保存图片为……",
49 | "save_image_as_error_description": "图片保存失败",
50 | "save_image_as_error_title": "图片保存失败"
51 | },
52 | "view_menu": {
53 | "actual_size": "实际大小",
54 | "toggle_developer_tools": "切换开发者工具",
55 | "toggle_full_screen": "切换全屏",
56 | "view": "查看"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "全部置前",
60 | "label": "窗口",
61 | "zoom": "放大"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/i18n/strings/zh_Hant.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": {
3 | "cancel": "取消",
4 | "close": "關閉",
5 | "close_brand": "關閉 %(brand)s",
6 | "copy": "複製",
7 | "cut": "剪下",
8 | "delete": "刪除",
9 | "edit": "編輯",
10 | "minimise": "最小化",
11 | "paste": "貼上",
12 | "paste_match_style": "貼上並保留格式",
13 | "quit": "離開",
14 | "redo": "取消復原",
15 | "select_all": "全選",
16 | "show_hide": "顯示/隱藏",
17 | "undo": "復原",
18 | "zoom_in": "放大",
19 | "zoom_out": "縮小"
20 | },
21 | "common": {
22 | "about": "關於",
23 | "brand_help": "%(brand)s 說明",
24 | "help": "說明",
25 | "preferences": "偏好設定"
26 | },
27 | "confirm_quit": "您確定要離開嗎?",
28 | "edit_menu": {
29 | "speech": "語音",
30 | "speech_start_speaking": "開始說話",
31 | "speech_stop_speaking": "停止說話"
32 | },
33 | "file_menu": {
34 | "label": "檔案"
35 | },
36 | "menu": {
37 | "hide": "隱藏",
38 | "hide_others": "隱藏其他",
39 | "services": "服務",
40 | "unhide": "取消隱藏"
41 | },
42 | "right_click_menu": {
43 | "add_to_dictionary": "新增到字典",
44 | "copy_email": "複製電子郵件地址",
45 | "copy_image": "複製圖片",
46 | "copy_image_url": "複製圖片地址",
47 | "copy_link_url": "複製連結",
48 | "save_image_as": "另存圖片為...",
49 | "save_image_as_error_description": "儲存圖片失敗",
50 | "save_image_as_error_title": "儲存圖片失敗"
51 | },
52 | "view_menu": {
53 | "actual_size": "實際大小",
54 | "toggle_developer_tools": "切換開發工具",
55 | "toggle_full_screen": "切換全螢幕",
56 | "view": "檢視"
57 | },
58 | "window_menu": {
59 | "bring_all_to_front": "全部移至最前",
60 | "label": "視窗",
61 | "zoom": "縮放"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/preload.cts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2018, 2019 , 2021 New Vector Ltd
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | // This file is compiled to CommonJS rather than ESM otherwise the browser chokes on the import statement.
10 |
11 | import { ipcRenderer, contextBridge, IpcRendererEvent } from "electron";
12 |
13 | // Expose only expected IPC wrapper APIs to the renderer process to avoid
14 | // handing out generalised messaging access.
15 |
16 | const CHANNELS = [
17 | "app_onAction",
18 | "before-quit",
19 | "check_updates",
20 | "install_update",
21 | "ipcCall",
22 | "ipcReply",
23 | "loudNotification",
24 | "preferences",
25 | "seshat",
26 | "seshatReply",
27 | "setBadgeCount",
28 | "update-downloaded",
29 | "userDownloadCompleted",
30 | "userDownloadAction",
31 | "openDesktopCapturerSourcePicker",
32 | "userAccessToken",
33 | "homeserverUrl",
34 | "serverSupportedVersions",
35 | ];
36 |
37 | contextBridge.exposeInMainWorld("electron", {
38 | on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
39 | if (!CHANNELS.includes(channel)) {
40 | console.error(`Unknown IPC channel ${channel} ignored`);
41 | return;
42 | }
43 | ipcRenderer.on(channel, listener);
44 | },
45 | send(channel: string, ...args: any[]): void {
46 | if (!CHANNELS.includes(channel)) {
47 | console.error(`Unknown IPC channel ${channel} ignored`);
48 | return;
49 | }
50 | ipcRenderer.send(channel, ...args);
51 | },
52 |
53 | async initialise(): Promise<{
54 | protocol: string;
55 | sessionId: string;
56 | config: IConfigOptions;
57 | supportedSettings: Record;
58 | }> {
59 | const [{ protocol, sessionId }, config, supportedSettings] = await Promise.all([
60 | ipcRenderer.invoke("getProtocol"),
61 | ipcRenderer.invoke("getConfig"),
62 | ipcRenderer.invoke("getSupportedSettings"),
63 | ]);
64 | return { protocol, sessionId, config, supportedSettings };
65 | },
66 |
67 | async setSettingValue(settingName: string, value: any): Promise {
68 | return ipcRenderer.invoke("setSettingValue", settingName, value);
69 | },
70 | async getSettingValue(settingName: string): Promise {
71 | return ipcRenderer.invoke("getSettingValue", settingName);
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/src/squirrelhooks.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 New Vector Ltd.
3 | Copyright 2017 OpenMarket Ltd
4 |
5 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6 | Please see LICENSE files in the repository root for full details.
7 | */
8 |
9 | import path from "node:path";
10 | import { spawn } from "node:child_process";
11 | import { app } from "electron";
12 |
13 | export function getSquirrelExecutable(): string {
14 | return path.resolve(path.dirname(process.execPath), "..", "Update.exe");
15 | }
16 |
17 | function runUpdateExe(args: string[]): Promise {
18 | // Invokes Squirrel's Update.exe which will do things for us like create shortcuts
19 | // Note that there's an Update.exe in the app-x.x.x directory and one in the parent
20 | // directory: we need to run the one in the parent directory, because it discovers
21 | // information about the app by inspecting the directory it's run from.
22 | const updateExe = getSquirrelExecutable();
23 | console.log(`Spawning '${updateExe}' with args '${args}'`);
24 | return new Promise((resolve) => {
25 | spawn(updateExe, args, {
26 | detached: true,
27 | }).on("close", resolve);
28 | });
29 | }
30 |
31 | function checkSquirrelHooks(): boolean {
32 | if (process.platform !== "win32") return false;
33 | const cmd = process.argv[1];
34 | const target = path.basename(process.execPath);
35 |
36 | switch (cmd) {
37 | case "--squirrel-install":
38 | void runUpdateExe(["--createShortcut=" + target]).then(() => app.quit());
39 | return true;
40 |
41 | case "--squirrel-updated":
42 | case "--squirrel-obsolete":
43 | app.quit();
44 | return true;
45 |
46 | case "--squirrel-uninstall":
47 | void runUpdateExe(["--removeShortcut=" + target]).then(() => app.quit());
48 | return true;
49 |
50 | default:
51 | return false;
52 | }
53 | }
54 |
55 | if (checkSquirrelHooks()) {
56 | process.exit(1);
57 | }
58 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022-2024 New Vector Ltd.
3 |
4 | SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5 | Please see LICENSE files in the repository root for full details.
6 | */
7 |
8 | import crypto from "node:crypto";
9 | import fs from "node:fs";
10 | import path from "node:path";
11 |
12 | export async function randomArray(size: number): Promise {
13 | return new Promise((resolve, reject) => {
14 | crypto.randomBytes(size, (err, buf) => {
15 | if (err) {
16 | reject(err);
17 | } else {
18 | resolve(buf.toString("base64").replace(/=+$/g, ""));
19 | }
20 | });
21 | });
22 | }
23 |
24 | type JsonValue = null | string | number;
25 | type JsonArray = Array;
26 | export interface JsonObject {
27 | [key: string]: JsonObject | JsonArray | JsonValue;
28 | }
29 | export type Json = JsonArray | JsonObject;
30 |
31 | /**
32 | * Synchronously load a JSON file from the local filesystem.
33 | * Unlike `require`, will never execute any javascript in a loaded file.
34 | * @param paths - An array of path segments which will be joined using the system's path delimiter.
35 | */
36 | export function loadJsonFile(...paths: string[]): T {
37 | const joinedPaths = path.join(...paths);
38 |
39 | if (!fs.existsSync(joinedPaths)) {
40 | console.log(`Skipping nonexistent file: ${joinedPaths}`);
41 | return {} as T;
42 | }
43 |
44 | const file = fs.readFileSync(joinedPaths, { encoding: "utf-8" });
45 | return JSON.parse(file);
46 | }
47 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "resolveJsonModule": true,
4 | "esModuleInterop": true,
5 | "module": "node16",
6 | "moduleResolution": "node16",
7 | "skipLibCheck": true,
8 | "target": "es2022",
9 | "sourceMap": false,
10 | "outDir": "./lib",
11 | "rootDir": "./src",
12 | "declaration": true,
13 | "typeRoots": ["src/@types", "node_modules/@types"],
14 | "lib": ["es2022"],
15 | "types": ["node"],
16 | "strict": true
17 | },
18 | "include": ["./src/**/*.ts", "./src/**/*.cts"]
19 | }
20 |
--------------------------------------------------------------------------------