├── .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 | --------------------------------------------------------------------------------