mirror of
https://github.com/balena-io/etcher.git
synced 2025-11-09 10:28:32 +00:00
Compare commits
105 Commits
v1.10.24
...
aethernet/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa30b34916 | ||
|
|
8cd6da1260 | ||
|
|
82dd4fc1d1 | ||
|
|
33fe4b2c1a | ||
|
|
b1c1188107 | ||
|
|
63b45aefae | ||
|
|
f79cb0fac5 | ||
|
|
ec42892c7c | ||
|
|
371716fe6a | ||
|
|
d5fb6bec15 | ||
|
|
c5e7bf26d7 | ||
|
|
e3072ac416 | ||
|
|
dfaf06e4cf | ||
|
|
6e24d25576 | ||
|
|
b59b171e43 | ||
|
|
28726584c2 | ||
|
|
00b151311a | ||
|
|
36c813714b | ||
|
|
2ae6764dd9 | ||
|
|
debefc9652 | ||
|
|
b068b847c7 | ||
|
|
6c410c07ce | ||
|
|
c01206c1f3 | ||
|
|
2e85fb45de | ||
|
|
67513e384d | ||
|
|
828dafa493 | ||
|
|
5c5a761222 | ||
|
|
fab10e5fc5 | ||
|
|
797345fc1c | ||
|
|
a0388a43c3 | ||
|
|
f5b0a3023b | ||
|
|
dc1d7bd1fd | ||
|
|
9d674321b6 | ||
|
|
f9c8378d6a | ||
|
|
65da751a52 | ||
|
|
72142be0de | ||
|
|
11cea7c926 | ||
|
|
8d46ee4c22 | ||
|
|
d63c09e2c2 | ||
|
|
c9e9d7d109 | ||
|
|
2412d20eb4 | ||
|
|
7f90d23a12 | ||
|
|
b9a82be29b | ||
|
|
638673ba5e | ||
|
|
898fe4f216 | ||
|
|
7e805662d1 | ||
|
|
baf59c73ac | ||
|
|
38ad9c97c6 | ||
|
|
8fc574f059 | ||
|
|
78b0f00e88 | ||
|
|
0f10f2d483 | ||
|
|
eb5f5bbb9e | ||
|
|
67d26ff790 | ||
|
|
17f2008d88 | ||
|
|
db1bf7e488 | ||
|
|
4b786b8a9f | ||
|
|
fdfa0d3258 | ||
|
|
757aa77d89 | ||
|
|
d70ea06565 | ||
|
|
f2ebd10053 | ||
|
|
cd67b442c9 | ||
|
|
852c83c4fb | ||
|
|
e3b2ee3b83 | ||
|
|
927a026b86 | ||
|
|
c851e1d54f | ||
|
|
e6fdca171f | ||
|
|
c9cfb87733 | ||
|
|
b0b7c53294 | ||
|
|
e8dc6579fe | ||
|
|
f0747abe3f | ||
|
|
32fab87340 | ||
|
|
adcd8e0325 | ||
|
|
7b5808eb2b | ||
|
|
a8f7422cf5 | ||
|
|
5ae9a26361 | ||
|
|
cf1fdb8c5f | ||
|
|
bf7ebde100 | ||
|
|
88c5fa5035 | ||
|
|
887b0dd538 | ||
|
|
364d1db56a | ||
|
|
c431222909 | ||
|
|
55a0f68b97 | ||
|
|
af2563dfc2 | ||
|
|
33f8851083 | ||
|
|
fe1f19b9fa | ||
|
|
871cf3ec0a | ||
|
|
686a5837b6 | ||
|
|
23f2dd5ce5 | ||
|
|
d5d39b395b | ||
|
|
2acad790d3 | ||
|
|
30133306d6 | ||
|
|
04a62f2ad8 | ||
|
|
17858a7d72 | ||
|
|
620307568f | ||
|
|
a349c5d9ac | ||
|
|
0d740ad12d | ||
|
|
85a3f28869 | ||
|
|
dbd5397405 | ||
|
|
85c183b9ef | ||
|
|
0d0af1d1dd | ||
|
|
ad423fc187 | ||
|
|
d8b2a7a236 | ||
|
|
13ec8cbe98 | ||
|
|
a7cae23612 | ||
|
|
86bb093f3d |
28
.github/actions/publish/action.yml
vendored
28
.github/actions/publish/action.yml
vendored
@@ -72,32 +72,6 @@ runs:
|
|||||||
chmod +x "$(dirname "$(which node)")/resinci-deploy" && which resinci-deploy
|
chmod +x "$(dirname "$(which node)")/resinci-deploy" && which resinci-deploy
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# FIXME: store sentry workflow is not documented
|
|
||||||
# https://github.com/product-os/resinci-deploy/blob/master/lib/sentry.ts
|
|
||||||
# https://github.com/getsentry/sentry-cli
|
|
||||||
# https://docs.sentry.io/api/projects/create-a-new-client-key/
|
|
||||||
- name: Generate Sentry DSN
|
|
||||||
id: sentry
|
|
||||||
shell: bash --noprofile --norc -eo pipefail -x {0}
|
|
||||||
run: |
|
|
||||||
set -ea
|
|
||||||
|
|
||||||
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
|
||||||
|
|
||||||
branch="$(echo '${{ github.event.pull_request.head.ref }}' | sed 's/[^[:alnum:]]/-/g')"
|
|
||||||
|
|
||||||
stdout="$(resinci-deploy store sentry \
|
|
||||||
--branch="${branch}" \
|
|
||||||
--name="$(jq -r '.name' package.json)" \
|
|
||||||
--team="$(yq e '.sentry.team' repo.yml)" \
|
|
||||||
--org="$(yq e '.sentry.org' repo.yml)" \
|
|
||||||
--type="$(yq e '.sentry.type' repo.yml)")"
|
|
||||||
|
|
||||||
echo "dsn=$(echo "${stdout}" | tail -n 1)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
env:
|
|
||||||
SENTRY_TOKEN: ${{ fromJSON(inputs.secrets).SENTRY_AUTH_TOKEN }}
|
|
||||||
|
|
||||||
# https://www.electron.build/code-signing.html
|
# https://www.electron.build/code-signing.html
|
||||||
# https://github.com/Apple-Actions/import-codesign-certs
|
# https://github.com/Apple-Actions/import-codesign-certs
|
||||||
- name: Import Apple code signing certificate
|
- name: Import Apple code signing certificate
|
||||||
@@ -171,7 +145,7 @@ runs:
|
|||||||
|
|
||||||
for target in ${TARGETS}; do
|
for target in ${TARGETS}; do
|
||||||
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
|
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
|
||||||
--c.extraMetadata.analytics.sentry.token='${{ steps.sentry.outputs.dsn }}' \
|
--c.extraMetadata.analytics.sentry.token='https://739bbcfc0ba4481481138d3fc831136d@o95242.ingest.sentry.io/4504451487301632' \
|
||||||
--c.extraMetadata.analytics.mixpanel.token='balena-etcher' \
|
--c.extraMetadata.analytics.mixpanel.token='balena-etcher' \
|
||||||
--c.extraMetadata.packageType="${target}"
|
--c.extraMetadata.packageType="${target}"
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/flowzone.yml
vendored
2
.github/workflows/flowzone.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
|
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
tests_run_on: '["ubuntu-18.04","macos-latest","windows-2019"]'
|
tests_run_on: '["ubuntu-20.04","macos-latest","windows-2019"]'
|
||||||
restrict_custom_actions: false
|
restrict_custom_actions: false
|
||||||
github_prerelease: true
|
github_prerelease: true
|
||||||
repo_config: true
|
repo_config: true
|
||||||
|
|||||||
13
.github/workflows/winget.yml
vendored
Normal file
13
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
name: Publish to WinGet
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: windows-latest # action can only be run on windows
|
||||||
|
steps:
|
||||||
|
- uses: vedantmgoyal2009/winget-releaser@v1
|
||||||
|
with:
|
||||||
|
identifier: Balena.Etcher
|
||||||
|
installers-regex: 'balenaEtcher-Setup.*.exe$'
|
||||||
|
token: ${{ secrets.WINGET_PAT }}
|
||||||
@@ -1,3 +1,457 @@
|
|||||||
|
- commits:
|
||||||
|
- subject: "patch: fixed winget parameter name"
|
||||||
|
hash: 33fe4b2c1abcb7f71f42a4b682f174422cb72a00
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: mcraa
|
||||||
|
nested: []
|
||||||
|
version: 1.13.2
|
||||||
|
title: ""
|
||||||
|
date: 2023-01-02T20:55:58.179Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: updated sdk to fix bz2 issue"
|
||||||
|
hash: f79cb0fac56f6ec86f3a6f9be7035fbf59102253
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Peter Makra
|
||||||
|
nested: []
|
||||||
|
- subject: "patch: update copyright in electron-builder"
|
||||||
|
hash: ec42892c7cf1d22f4bb2968733ba9dc85e3552c6
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: JOASSART Edwin
|
||||||
|
nested: []
|
||||||
|
version: 1.13.1
|
||||||
|
title: ""
|
||||||
|
date: 2023-01-02T17:26:55.602Z
|
||||||
|
- commits:
|
||||||
|
- subject: "minor: electron version bump"
|
||||||
|
hash: e3072ac416470cd161309935c2622cc5a3d8d242
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Peter Makra
|
||||||
|
nested: []
|
||||||
|
- subject: "patch: handle ext2fs with webpack"
|
||||||
|
hash: b59b171e43a170c0ccf84d36ab4ba2db403e25c5
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Peter Makra
|
||||||
|
nested: []
|
||||||
|
- subject: "Patch: update etcher-sdk version to fix CM4 issues"
|
||||||
|
hash: 36c813714b3ce60e5e85f1889c9bc7eeadb524b4
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: builder555
|
||||||
|
nested: []
|
||||||
|
version: 1.13.0
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-28T16:48:12.506Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency i18next to 21.10.0
|
||||||
|
hash: b068b847c7ae6456ebd16a5d641ff61d8d074015
|
||||||
|
body: |
|
||||||
|
Update i18next to 21.10.0
|
||||||
|
|
||||||
|
Update i18next from 21.8.14 to 21.10.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.12.7
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-20T19:35:11.219Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency react-i18next to 11.18.6
|
||||||
|
hash: 2e85fb45de3a52e9bf0605e474a550e1af2bb980
|
||||||
|
body: |
|
||||||
|
Update react-i18next to 11.18.6
|
||||||
|
|
||||||
|
Update react-i18next from 11.18.1 to 11.18.6
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.12.6
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-20T18:00:03.165Z
|
||||||
|
- commits:
|
||||||
|
- subject: "Patch: made trim setting more readable"
|
||||||
|
hash: 5c5a7612220b011e6942ac10b7026b7d6513e54f
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: builder555
|
||||||
|
nested: []
|
||||||
|
version: 1.12.5
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-20T12:27:33.706Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: publish to winget with gh action"
|
||||||
|
hash: f9c8378d6a96e1c44d0bced0d3227a9930b9fd14
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Begula
|
||||||
|
nested: []
|
||||||
|
version: 1.12.4
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-19T19:41:59.151Z
|
||||||
|
- commits:
|
||||||
|
- subject: "Patch: replaced plain text with i18n in settings"
|
||||||
|
hash: 11cea7c926be1bac27df8ab583cbb4b8752a49c5
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: builder555
|
||||||
|
nested: []
|
||||||
|
version: 1.12.3
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-19T09:51:52.545Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency webpack-dev-server to 4.11.1
|
||||||
|
hash: c9e9d7d109ee103402acea1c7be4bac2f674168a
|
||||||
|
body: |
|
||||||
|
Update webpack-dev-server to 4.11.1
|
||||||
|
|
||||||
|
Update webpack-dev-server from 4.5.0 to 4.11.1
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.12.2
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-16T16:59:03.254Z
|
||||||
|
- commits:
|
||||||
|
- subject: "Patch: expose trim ext{2,3,4} setting"
|
||||||
|
hash: b9a82be29bee61a41f6f6a5ca6043dd8a7bee547
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: builder555
|
||||||
|
nested: []
|
||||||
|
version: 1.12.1
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-16T15:02:34.104Z
|
||||||
|
- commits:
|
||||||
|
- subject: i18n support and Chinese translation
|
||||||
|
hash: 8fc574f0596f256382523c3b269e647a9aed5747
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: minor
|
||||||
|
change-type: minor
|
||||||
|
author: ab77
|
||||||
|
nested: []
|
||||||
|
- subject: "minor: optimize i18n"
|
||||||
|
hash: 67d26ff7902c272e8113c9b650382a1dcb0cdde0
|
||||||
|
body: >
|
||||||
|
Optimized several translations.
|
||||||
|
|
||||||
|
This commit itself is only a patch, but as a pull request must have at least one commit with a change-type.
|
||||||
|
footer:
|
||||||
|
Change-Type: minor
|
||||||
|
change-type: minor
|
||||||
|
author: r-q
|
||||||
|
nested: []
|
||||||
|
version: 1.12.0
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-14T16:17:31.131Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency webpack-cli to 4.10.0
|
||||||
|
hash: 757aa77d897d6266afbf6e7d62ddd0255ea0c926
|
||||||
|
body: |
|
||||||
|
Update webpack-cli to 4.10.0
|
||||||
|
|
||||||
|
Update webpack-cli from 4.2.0 to 4.10.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.10
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-13T02:27:41.593Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency webpack to 5.75.0
|
||||||
|
hash: cd67b442c9c55adddb2d8def456d7155af1f23c6
|
||||||
|
body: |
|
||||||
|
Update webpack to 5.75.0
|
||||||
|
|
||||||
|
Update webpack from 5.11.0 to 5.75.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.9
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-12T23:58:09.807Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency awscli to 1.27.28
|
||||||
|
hash: 927a026b869e0da872e17a662536d0ad3aeeae0f
|
||||||
|
body: |
|
||||||
|
Update awscli to 1.27.28
|
||||||
|
|
||||||
|
Update awscli from 1.27.27 to 1.27.28
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.8
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-12T21:55:28.620Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency uuid to 8.3.2
|
||||||
|
hash: c9cfb87733a694a69044a3b04cb12e72047a92da
|
||||||
|
body: |
|
||||||
|
Update uuid to 8.3.2
|
||||||
|
|
||||||
|
Update uuid from 8.1.0 to 8.3.2
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.7
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-12T19:57:31.379Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency tslib to 2.4.1
|
||||||
|
hash: f0747abe3fde8df4a004573a3ffd49134dd6d40d
|
||||||
|
body: |
|
||||||
|
Update tslib to 2.4.1
|
||||||
|
|
||||||
|
Update tslib from 2.0.0 to 2.4.1
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
- subject: "Patch: run linux build on ubuntu-20.04"
|
||||||
|
hash: adcd8e0325bc891460b3e51aa5403f8675189f13
|
||||||
|
body: >-
|
||||||
|
as [`18.04` has been
|
||||||
|
removed](https://github.blog/changelog/2022-08-09-github-actions-the-ubuntu-18-04-actions-runner-image-is-being-deprecated-and-will-be-removed-by-12-1-22/)
|
||||||
|
|
||||||
|
|
||||||
|
We cannot use `latest` as the glibc version will cause issue with older ubuntu version.
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.11.6
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-12T17:59:43.391Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency ts-loader to 8.4.0
|
||||||
|
hash: 5ae9a263619e9bb499705ee7c19df5707ea3936c
|
||||||
|
body: |
|
||||||
|
Update ts-loader to 8.4.0
|
||||||
|
|
||||||
|
Update ts-loader from 8.0.12 to 8.4.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.5
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T12:28:18.918Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency styled-components to 5.3.6
|
||||||
|
hash: 88c5fa50352efe76a6506ee763915f504889111b
|
||||||
|
body: |
|
||||||
|
Update styled-components to 5.3.6
|
||||||
|
|
||||||
|
Update styled-components from 5.1.0 to 5.3.6
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.4
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T10:57:10.809Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency terser-webpack-plugin to 5.3.6
|
||||||
|
hash: c431222909ef49126549c0f630cf1f3f3f8895ed
|
||||||
|
body: |
|
||||||
|
Update terser-webpack-plugin to 5.3.6
|
||||||
|
|
||||||
|
Update terser-webpack-plugin from 5.2.5 to 5.3.6
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.3
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T08:54:53.274Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency string-replace-loader to 3.1.0
|
||||||
|
hash: 33f8851083d50ed2db2b1308d1f1cf8749751db5
|
||||||
|
body: |
|
||||||
|
Update string-replace-loader to 3.1.0
|
||||||
|
|
||||||
|
Update string-replace-loader from 3.0.1 to 3.1.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.2
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T07:32:25.553Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency sinon to 9.2.4
|
||||||
|
hash: 686a5837b69c26bc83772ed738376ce0a766fef5
|
||||||
|
body: |
|
||||||
|
Update sinon to 9.2.4
|
||||||
|
|
||||||
|
Update sinon from 9.0.2 to 9.2.4
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.1
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T06:19:30.932Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency shyaml to 0.6.2
|
||||||
|
hash: 2acad790d3e85b3215e692705691fff4fadb99b0
|
||||||
|
body: |
|
||||||
|
Update shyaml to 0.6.2
|
||||||
|
|
||||||
|
Update shyaml from 0.5.0 to 0.6.2
|
||||||
|
footer:
|
||||||
|
Change-type: minor
|
||||||
|
change-type: minor
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.11.0
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T04:27:19.119Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency awscli to 1.27.27
|
||||||
|
hash: 17858a7d72dc001419bf75ac04b1a66b782404e2
|
||||||
|
body: |
|
||||||
|
Update awscli to 1.27.27
|
||||||
|
|
||||||
|
Update awscli from 1.27.26 to 1.27.27
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.10.29
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T03:05:02.826Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency rendition to 19.3.2
|
||||||
|
hash: 0d740ad12dbadcb35d0e2076821a073177476acb
|
||||||
|
body: |
|
||||||
|
Update rendition to 19.3.2
|
||||||
|
|
||||||
|
Update rendition from 19.2.0 to 19.3.2
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested:
|
||||||
|
- commits:
|
||||||
|
- subject: Add Breadcrumbs component export
|
||||||
|
hash: fbe0da641ae76ed99185f5799f94653bbed52609
|
||||||
|
body: |
|
||||||
|
add breadcrumbs component export
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Andrea Rosci <andrear@balena.io>
|
||||||
|
signed-off-by: Andrea Rosci <andrear@balena.io>
|
||||||
|
author: JSReds
|
||||||
|
version: rendition-19.3.2
|
||||||
|
date: 2020-12-29T17:27:09.528Z
|
||||||
|
- commits:
|
||||||
|
- subject: Fix max-width on breadcrumbs container
|
||||||
|
hash: b7c4ab7bed3caa1c10130f50abb582fcf63d867e
|
||||||
|
body: |
|
||||||
|
fix max-width on breadcrumbs container
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Andrea Rosci <andrear@balena.io>
|
||||||
|
signed-off-by: Andrea Rosci <andrear@balena.io>
|
||||||
|
author: JSReds
|
||||||
|
version: rendition-19.3.1
|
||||||
|
date: 2020-12-29T13:35:01.615Z
|
||||||
|
- commits:
|
||||||
|
- subject: Add Breadcrumbs component
|
||||||
|
hash: 41a7abb3724a6c8a0a839d4083ce36ceaeeb9d91
|
||||||
|
body: |
|
||||||
|
Add Breadcrumbs component and generate snapshots
|
||||||
|
footer:
|
||||||
|
Change-type: minor
|
||||||
|
change-type: minor
|
||||||
|
Signed-off-by: Andrea Rosci <andrear@balena.io>
|
||||||
|
signed-off-by: Andrea Rosci <andrear@balena.io>
|
||||||
|
author: JSReds
|
||||||
|
version: rendition-19.3.0
|
||||||
|
date: 2020-12-29T12:12:16.944Z
|
||||||
|
version: 1.10.28
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-10T02:10:41.368Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency redux to 4.2.0
|
||||||
|
hash: 85c183b9ef2dbe798c6d5cd8bce6367ef31e2624
|
||||||
|
body: |
|
||||||
|
Update redux to 4.2.0
|
||||||
|
|
||||||
|
Update redux from 4.0.5 to 4.2.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.10.27
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-09T20:59:24.075Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency pretty-bytes to 5.6.0
|
||||||
|
hash: d8b2a7a2369f24b1390bd85eca3cfbcd8282df80
|
||||||
|
body: |
|
||||||
|
Update pretty-bytes to 5.6.0
|
||||||
|
|
||||||
|
Update pretty-bytes from 5.3.0 to 5.6.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.10.26
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-09T18:58:21.970Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency pnp-webpack-plugin to 1.7.0
|
||||||
|
hash: 86bb093f3d7eec4835060789cc725e172223c74f
|
||||||
|
body: |
|
||||||
|
Update pnp-webpack-plugin to 1.7.0
|
||||||
|
|
||||||
|
Update pnp-webpack-plugin from 1.6.4 to 1.7.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: 1.10.25
|
||||||
|
title: ""
|
||||||
|
date: 2022-12-09T17:01:52.897Z
|
||||||
- commits:
|
- commits:
|
||||||
- subject: Update dependency node-ipc to 9.2.1
|
- subject: Update dependency node-ipc to 9.2.1
|
||||||
hash: f26b0748114c7623f152e00b065f3e6ec0657e83
|
hash: f26b0748114c7623f152e00b065f3e6ec0657e83
|
||||||
|
|||||||
160
CHANGELOG.md
160
CHANGELOG.md
@@ -3,6 +3,166 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
# v1.13.2
|
||||||
|
## (2023-01-02)
|
||||||
|
|
||||||
|
* patch: fixed winget parameter name [mcraa]
|
||||||
|
|
||||||
|
# v1.13.1
|
||||||
|
## (2023-01-02)
|
||||||
|
|
||||||
|
* patch: updated sdk to fix bz2 issue [Peter Makra]
|
||||||
|
* patch: update copyright in electron-builder [JOASSART Edwin]
|
||||||
|
|
||||||
|
# v1.13.0
|
||||||
|
## (2022-12-28)
|
||||||
|
|
||||||
|
* minor: electron version bump [Peter Makra]
|
||||||
|
* patch: handle ext2fs with webpack [Peter Makra]
|
||||||
|
* Patch: update etcher-sdk version to fix CM4 issues [builder555]
|
||||||
|
|
||||||
|
# v1.12.7
|
||||||
|
## (2022-12-20)
|
||||||
|
|
||||||
|
* Update dependency i18next to 21.10.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.12.6
|
||||||
|
## (2022-12-20)
|
||||||
|
|
||||||
|
* Update dependency react-i18next to 11.18.6 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.12.5
|
||||||
|
## (2022-12-20)
|
||||||
|
|
||||||
|
* Patch: made trim setting more readable [builder555]
|
||||||
|
|
||||||
|
# v1.12.4
|
||||||
|
## (2022-12-19)
|
||||||
|
|
||||||
|
* patch: publish to winget with gh action [Begula]
|
||||||
|
|
||||||
|
# v1.12.3
|
||||||
|
## (2022-12-19)
|
||||||
|
|
||||||
|
* Patch: replaced plain text with i18n in settings [builder555]
|
||||||
|
|
||||||
|
# v1.12.2
|
||||||
|
## (2022-12-16)
|
||||||
|
|
||||||
|
* Update dependency webpack-dev-server to 4.11.1 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.12.1
|
||||||
|
## (2022-12-16)
|
||||||
|
|
||||||
|
* Patch: expose trim ext{2,3,4} setting [builder555]
|
||||||
|
|
||||||
|
# v1.12.0
|
||||||
|
## (2022-12-14)
|
||||||
|
|
||||||
|
* i18n support and Chinese translation [ab77]
|
||||||
|
* minor: optimize i18n [r-q]
|
||||||
|
|
||||||
|
# v1.11.10
|
||||||
|
## (2022-12-13)
|
||||||
|
|
||||||
|
* Update dependency webpack-cli to 4.10.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.9
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency webpack to 5.75.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.8
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.28 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.7
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency uuid to 8.3.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.6
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency tslib to 2.4.1 [Renovate Bot]
|
||||||
|
* Patch: run linux build on ubuntu-20.04 [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.11.5
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency ts-loader to 8.4.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.4
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency styled-components to 5.3.6 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.3
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency terser-webpack-plugin to 5.3.6 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.2
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency string-replace-loader to 3.1.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.1
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency sinon to 9.2.4 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.0
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency shyaml to 0.6.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.29
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.27 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.28
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Update dependency rendition to 19.3.2 [Renovate Bot] </summary>
|
||||||
|
|
||||||
|
> ## rendition-19.3.2
|
||||||
|
> ### (2020-12-29)
|
||||||
|
>
|
||||||
|
> * Add Breadcrumbs component export [JSReds]
|
||||||
|
>
|
||||||
|
> ## rendition-19.3.1
|
||||||
|
> ### (2020-12-29)
|
||||||
|
>
|
||||||
|
> * Fix max-width on breadcrumbs container [JSReds]
|
||||||
|
>
|
||||||
|
> ## rendition-19.3.0
|
||||||
|
> ### (2020-12-29)
|
||||||
|
>
|
||||||
|
> * Add Breadcrumbs component [JSReds]
|
||||||
|
>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# v1.10.27
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency redux to 4.2.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.26
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency pretty-bytes to 5.6.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.25
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency pnp-webpack-plugin to 1.7.0 [Renovate Bot]
|
||||||
|
|
||||||
# v1.10.24
|
# v1.10.24
|
||||||
## (2022-12-09)
|
## (2022-12-09)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
# https://www.electron.build/configuration/configuration
|
# https://www.electron.build/configuration/configuration
|
||||||
appId: io.balena.etcher
|
appId: io.balena.etcher
|
||||||
copyright: Copyright 2016-2021 Balena Ltd
|
copyright: Copyright 2016-2023 Balena Ltd
|
||||||
productName: balenaEtcher
|
productName: balenaEtcher
|
||||||
afterPack: ./afterPack.js
|
afterPack: ./afterPack.js
|
||||||
afterSign: ./afterSignHook.js
|
afterSign: ./afterSignHook.js
|
||||||
asar: false
|
asar: false
|
||||||
files:
|
files:
|
||||||
- generated
|
- generated
|
||||||
- lib/shared/catalina-sudo/sudo-askpass.osascript.js
|
- lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
|
||||||
|
- lib/shared/catalina-sudo/sudo-askpass.osascript-en.js
|
||||||
mac:
|
mac:
|
||||||
icon: assets/icon.icns
|
icon: assets/icon.icns
|
||||||
category: public.app-category.developer-tools
|
category: public.app-category.developer-tools
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import * as osDialog from './os/dialog';
|
|||||||
import * as windowProgress from './os/window-progress';
|
import * as windowProgress from './os/window-progress';
|
||||||
import MainPage from './pages/main/MainPage';
|
import MainPage from './pages/main/MainPage';
|
||||||
import './css/main.css';
|
import './css/main.css';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'unhandledrejection',
|
'unhandledrejection',
|
||||||
@@ -313,9 +314,9 @@ window.addEventListener('beforeunload', async (event) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const confirmed = await osDialog.showWarning({
|
const confirmed = await osDialog.showWarning({
|
||||||
confirmationLabel: 'Yes, quit',
|
confirmationLabel: i18next.t('yesExit'),
|
||||||
rejectionLabel: 'Cancel',
|
rejectionLabel: i18next.t('cancel'),
|
||||||
title: 'Are you sure you want to close Etcher?',
|
title: i18next.t('reallyExit'),
|
||||||
description: messages.warning.exitWhileFlashing(),
|
description: messages.warning.exitWhileFlashing(),
|
||||||
});
|
});
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
|
|
||||||
import { SourceMetadata } from '../source-selector/source-selector';
|
import { SourceMetadata } from '../source-selector/source-selector';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
interface UsbbootDrive extends sourceDestination.UsbbootDrive {
|
interface UsbbootDrive extends sourceDestination.UsbbootDrive {
|
||||||
progress: number;
|
progress: number;
|
||||||
@@ -189,7 +190,7 @@ export class DriveSelector extends React.Component<
|
|||||||
this.tableColumns = [
|
this.tableColumns = [
|
||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
label: 'Name',
|
label: i18next.t('drives.name'),
|
||||||
render: (description: string, drive: Drive) => {
|
render: (description: string, drive: Drive) => {
|
||||||
if (isDrivelistDrive(drive)) {
|
if (isDrivelistDrive(drive)) {
|
||||||
const isLargeDrive = isDriveSizeLarge(drive);
|
const isLargeDrive = isDriveSizeLarge(drive);
|
||||||
@@ -215,7 +216,7 @@ export class DriveSelector extends React.Component<
|
|||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
key: 'size',
|
key: 'size',
|
||||||
label: 'Size',
|
label: i18next.t('drives.size'),
|
||||||
render: (_description: string, drive: Drive) => {
|
render: (_description: string, drive: Drive) => {
|
||||||
if (isDrivelistDrive(drive) && drive.size !== null) {
|
if (isDrivelistDrive(drive) && drive.size !== null) {
|
||||||
return prettyBytes(drive.size);
|
return prettyBytes(drive.size);
|
||||||
@@ -225,7 +226,7 @@ export class DriveSelector extends React.Component<
|
|||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
key: 'link',
|
key: 'link',
|
||||||
label: 'Location',
|
label: i18next.t('drives.location'),
|
||||||
render: (_description: string, drive: Drive) => {
|
render: (_description: string, drive: Drive) => {
|
||||||
return (
|
return (
|
||||||
<Txt>
|
<Txt>
|
||||||
@@ -399,14 +400,14 @@ export class DriveSelector extends React.Component<
|
|||||||
color="#5b82a7"
|
color="#5b82a7"
|
||||||
style={{ fontWeight: 600 }}
|
style={{ fontWeight: 600 }}
|
||||||
>
|
>
|
||||||
{drives.length} found
|
{i18next.t('drives.find', { length: drives.length })}
|
||||||
</Txt>
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
|
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
|
||||||
cancel={() => cancel(this.originalList)}
|
cancel={() => cancel(this.originalList)}
|
||||||
done={() => done(selectedList)}
|
done={() => done(selectedList)}
|
||||||
action={`Select (${selectedList.length})`}
|
action={i18next.t('drives.select', { select: selectedList.length })}
|
||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
primary: !showWarnings,
|
primary: !showWarnings,
|
||||||
warning: showWarnings,
|
warning: showWarnings,
|
||||||
@@ -512,7 +513,11 @@ export class DriveSelector extends React.Component<
|
|||||||
>
|
>
|
||||||
<Flex alignItems="center">
|
<Flex alignItems="center">
|
||||||
<ChevronDownSvg height="1em" fill="currentColor" />
|
<ChevronDownSvg height="1em" fill="currentColor" />
|
||||||
<Txt ml={8}>Show {numberOfHiddenSystemDrives} hidden</Txt>
|
<Txt ml={8}>
|
||||||
|
{i18next.t('drives.showHidden', {
|
||||||
|
num: numberOfHiddenSystemDrives,
|
||||||
|
})}
|
||||||
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -520,7 +525,7 @@ export class DriveSelector extends React.Component<
|
|||||||
)}
|
)}
|
||||||
{this.props.showWarnings && hasSystemDrives ? (
|
{this.props.showWarnings && hasSystemDrives ? (
|
||||||
<Alert className="system-drive-alert" style={{ width: '67%' }}>
|
<Alert className="system-drive-alert" style={{ width: '67%' }}>
|
||||||
Selecting your system drive is dangerous and will erase your drive!
|
{i18next.t('drives.systemDriveDanger')}
|
||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -540,13 +545,15 @@ export class DriveSelector extends React.Component<
|
|||||||
this.setState({ missingDriversModal: {} });
|
this.setState({ missingDriversModal: {} });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
action="Yes, continue"
|
action={i18next.t('yesContinue')}
|
||||||
cancelButtonProps={{
|
cancelButtonProps={{
|
||||||
children: 'Cancel',
|
children: i18next.t('cancel'),
|
||||||
}}
|
}}
|
||||||
children={
|
children={
|
||||||
missingDriversModal.drive.linkMessage ||
|
missingDriversModal.drive.linkMessage ||
|
||||||
`Etcher will open ${missingDriversModal.drive.link} in your browser`
|
i18next.t('drives.openInBrowser', {
|
||||||
|
link: missingDriversModal.drive.link,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { middleEllipsis } from '../../utils/middle-ellipsis';
|
|||||||
|
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
import { DriveWithWarnings } from '../../pages/main/Flash';
|
import { DriveWithWarnings } from '../../pages/main/Flash';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const DriveStatusWarningModal = ({
|
const DriveStatusWarningModal = ({
|
||||||
done,
|
done,
|
||||||
@@ -17,12 +18,12 @@ const DriveStatusWarningModal = ({
|
|||||||
isSystem: boolean;
|
isSystem: boolean;
|
||||||
drivesWithWarnings: DriveWithWarnings[];
|
drivesWithWarnings: DriveWithWarnings[];
|
||||||
}) => {
|
}) => {
|
||||||
let warningSubtitle = 'You are about to erase an unusually large drive';
|
let warningSubtitle = i18next.t('drives.largeDriveWarning');
|
||||||
let warningCta = 'Are you sure the selected drive is not a storage drive?';
|
let warningCta = i18next.t('drives.largeDriveWarningMsg');
|
||||||
|
|
||||||
if (isSystem) {
|
if (isSystem) {
|
||||||
warningSubtitle = "You are about to erase your computer's drives";
|
warningSubtitle = i18next.t('drives.systemDriveWarning');
|
||||||
warningCta = 'Are you sure you want to flash your system drive?';
|
warningCta = i18next.t('drives.systemDriveWarningMsg');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -33,9 +34,9 @@ const DriveStatusWarningModal = ({
|
|||||||
cancelButtonProps={{
|
cancelButtonProps={{
|
||||||
primary: false,
|
primary: false,
|
||||||
warning: true,
|
warning: true,
|
||||||
children: 'Change target',
|
children: i18next.t('drives.changeTarget'),
|
||||||
}}
|
}}
|
||||||
action={"Yes, I'm sure"}
|
action={i18next.t('sure')}
|
||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
primary: false,
|
primary: false,
|
||||||
outline: true,
|
outline: true,
|
||||||
@@ -50,7 +51,7 @@ const DriveStatusWarningModal = ({
|
|||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<ExclamationTriangleSvg height="2em" fill="#fca321" />
|
<ExclamationTriangleSvg height="2em" fill="#fca321" />
|
||||||
<Txt fontSize="24px" color="#fca321">
|
<Txt fontSize="24px" color="#fca321">
|
||||||
WARNING!
|
{i18next.t('warning')}
|
||||||
</Txt>
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Txt fontSize="24px">{warningSubtitle}</Txt>
|
<Txt fontSize="24px">{warningSubtitle}</Txt>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { BaseButton } from '../../styled-components';
|
import { BaseButton } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export interface FlashAnotherProps {
|
export interface FlashAnotherProps {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -25,7 +26,7 @@ export interface FlashAnotherProps {
|
|||||||
export const FlashAnother = (props: FlashAnotherProps) => {
|
export const FlashAnother = (props: FlashAnotherProps) => {
|
||||||
return (
|
return (
|
||||||
<BaseButton primary onClick={props.onClick}>
|
<BaseButton primary onClick={props.onClick}>
|
||||||
Flash another
|
{i18next.t('flash.another')}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { resetState } from '../../models/flash-state';
|
|||||||
import * as selection from '../../models/selection-state';
|
import * as selection from '../../models/selection-state';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
import { Modal, Table } from '../../styled-components';
|
import { Modal, Table } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)`
|
const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)`
|
||||||
&&& [data-display='table-head'],
|
&&& [data-display='table-head'],
|
||||||
@@ -88,15 +89,15 @@ function formattedErrors(errors: FlashError[]) {
|
|||||||
const columns: Array<TableColumn<FlashError>> = [
|
const columns: Array<TableColumn<FlashError>> = [
|
||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
label: 'Target',
|
label: i18next.t('flash.target'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'device',
|
field: 'device',
|
||||||
label: 'Location',
|
label: i18next.t('flash.location'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'message',
|
field: 'message',
|
||||||
label: 'Error',
|
label: i18next.t('flash.error'),
|
||||||
render: (message: string, { code }: FlashError) => {
|
render: (message: string, { code }: FlashError) => {
|
||||||
return message ?? code;
|
return message ?? code;
|
||||||
},
|
},
|
||||||
@@ -162,9 +163,11 @@ export function FlashResults({
|
|||||||
<Txt>{middleEllipsis(image, 24)}</Txt>
|
<Txt>{middleEllipsis(image, 24)}</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Txt fontSize={24} color="#fff" mb="17px">
|
<Txt fontSize={24} color="#fff" mb="17px">
|
||||||
Flash {allFailed ? 'Failed' : 'Complete'}!
|
{allFailed
|
||||||
|
? i18next.t('flash.flashFailed')
|
||||||
|
: i18next.t('flash.flashCompleted')}
|
||||||
</Txt>
|
</Txt>
|
||||||
{skip ? <Txt color="#7e8085">Validation has been skipped</Txt> : null}
|
{skip ? <Txt color="#7e8085">{i18next.t('flash.skip')}</Txt> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flexDirection="column" color="#7e8085">
|
<Flex flexDirection="column" color="#7e8085">
|
||||||
{results.devices.successful !== 0 ? (
|
{results.devices.successful !== 0 ? (
|
||||||
@@ -188,7 +191,7 @@ export function FlashResults({
|
|||||||
{progress.failed(errors.length)}
|
{progress.failed(errors.length)}
|
||||||
</Txt>
|
</Txt>
|
||||||
<Link ml="10px" onClick={() => setShowErrorsInfo(true)}>
|
<Link ml="10px" onClick={() => setShowErrorsInfo(true)}>
|
||||||
more info
|
{i18next.t('flash.moreInfo')}
|
||||||
</Link>
|
</Link>
|
||||||
</Flex>
|
</Flex>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -199,12 +202,9 @@ export function FlashResults({
|
|||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
tooltip={outdent({ newline: ' ' })`
|
tooltip={i18next.t('flash.speedTip')}
|
||||||
The speed is calculated by dividing the image size by the flashing time.
|
|
||||||
Disk images with ext partitions flash faster as we are able to skip unused parts.
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Effective speed: {effectiveSpeed} MB/s
|
{i18next.t('flash.speed', { speed: effectiveSpeed })}
|
||||||
</Txt>
|
</Txt>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -214,11 +214,11 @@ export function FlashResults({
|
|||||||
titleElement={
|
titleElement={
|
||||||
<Flex alignItems="baseline" mb={18}>
|
<Flex alignItems="baseline" mb={18}>
|
||||||
<Txt fontSize={24} align="left">
|
<Txt fontSize={24} align="left">
|
||||||
Failed targets
|
{i18next.t('failedTarget')}
|
||||||
</Txt>
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
action="Retry failed targets"
|
action={i18next.t('failedRetry')}
|
||||||
cancel={() => setShowErrorsInfo(false)}
|
cancel={() => setShowErrorsInfo(false)}
|
||||||
done={() => {
|
done={() => {
|
||||||
setShowErrorsInfo(false);
|
setShowErrorsInfo(false);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { default as styled } from 'styled-components';
|
|||||||
|
|
||||||
import { fromFlashState } from '../../modules/progress-status';
|
import { fromFlashState } from '../../modules/progress-status';
|
||||||
import { StepButton } from '../../styled-components';
|
import { StepButton } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const FlashProgressBar = styled(ProgressBar)`
|
const FlashProgressBar = styled(ProgressBar)`
|
||||||
> div {
|
> div {
|
||||||
@@ -28,6 +29,7 @@ const FlashProgressBar = styled(ProgressBar)`
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
text-shadow: none !important;
|
text-shadow: none !important;
|
||||||
transition-duration: 0s;
|
transition-duration: 0s;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
transition-duration: 0s;
|
transition-duration: 0s;
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,7 @@ const colors = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const CancelButton = styled(({ type, onClick, ...props }) => {
|
const CancelButton = styled(({ type, onClick, ...props }) => {
|
||||||
const status = type === 'verifying' ? 'Skip' : 'Cancel';
|
const status = type === 'verifying' ? i18next.t('skip') : i18next.t('cancel');
|
||||||
return (
|
return (
|
||||||
<Button plain onClick={() => onClick(status)} {...props}>
|
<Button plain onClick={() => onClick(status)} {...props}>
|
||||||
{status}
|
{status}
|
||||||
@@ -69,6 +71,7 @@ const CancelButton = styled(({ type, onClick, ...props }) => {
|
|||||||
);
|
);
|
||||||
})`
|
})`
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
&&& {
|
&&& {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -126,7 +129,7 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
|||||||
marginTop: 30,
|
marginTop: 30,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Flash!
|
{i18next.t('flash.flashNow')}
|
||||||
</StepButton>
|
</StepButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import * as settings from '../../models/settings';
|
|||||||
import * as analytics from '../../modules/analytics';
|
import * as analytics from '../../modules/analytics';
|
||||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||||
import { Modal } from '../../styled-components';
|
import { Modal } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
interface Setting {
|
interface Setting {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -34,13 +35,17 @@ async function getSettingsList(): Promise<Setting[]> {
|
|||||||
const list: Setting[] = [
|
const list: Setting[] = [
|
||||||
{
|
{
|
||||||
name: 'errorReporting',
|
name: 'errorReporting',
|
||||||
label: 'Anonymously report errors and usage statistics to balena.io',
|
label: i18next.t('settings.errorReporting'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'autoBlockmapping',
|
||||||
|
label: i18next.t('settings.trimExtPartitions'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (['appimage', 'nsis', 'dmg'].includes(packageType)) {
|
if (['appimage', 'nsis', 'dmg'].includes(packageType)) {
|
||||||
list.push({
|
list.push({
|
||||||
name: 'updatesEnabled',
|
name: 'updatesEnabled',
|
||||||
label: 'Auto-updates enabled',
|
label: i18next.t('settings.autoUpdate'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
@@ -58,6 +63,7 @@ const InfoBox = (props: any) => (
|
|||||||
<TextWithCopy code text={props.value} copy={props.value} />
|
<TextWithCopy code text={props.value} copy={props.value} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
||||||
const [settingsList, setCurrentSettingsList] = React.useState<Setting[]>([]);
|
const [settingsList, setCurrentSettingsList] = React.useState<Setting[]>([]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -92,7 +98,7 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
|||||||
<Modal
|
<Modal
|
||||||
titleElement={
|
titleElement={
|
||||||
<Txt fontSize={24} mb={24}>
|
<Txt fontSize={24} mb={24}>
|
||||||
Settings
|
{i18next.t('settings.settings')}
|
||||||
</Txt>
|
</Txt>
|
||||||
}
|
}
|
||||||
done={() => toggleModal(false)}
|
done={() => toggleModal(false)}
|
||||||
@@ -113,7 +119,7 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
|||||||
})}
|
})}
|
||||||
{UUID !== undefined && (
|
{UUID !== undefined && (
|
||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<Txt fontSize={24}>System Information</Txt>
|
<Txt fontSize={24}>{i18next.t('settings.systemInformation')}</Txt>
|
||||||
<InfoBox label="UUID" value={UUID.substr(0, 7)} />
|
<InfoBox label="UUID" value={UUID.substr(0, 7)} />
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import { DriveSelector } from '../drive-selector/drive-selector';
|
|||||||
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import { isJson } from '../../../../shared/utils';
|
import { isJson } from '../../../../shared/utils';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const recentUrlImagesKey = 'recentUrlImages';
|
const recentUrlImagesKey = 'recentUrlImages';
|
||||||
|
|
||||||
@@ -160,7 +161,7 @@ const URLSelector = ({
|
|||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
disabled: loading || !imageURL,
|
disabled: loading || !imageURL,
|
||||||
}}
|
}}
|
||||||
action={loading ? <Spinner /> : 'OK'}
|
action={loading ? <Spinner /> : i18next.t('ok')}
|
||||||
done={async () => {
|
done={async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const urlStrings = recentImages.map((url: URL) => url.href);
|
const urlStrings = recentImages.map((url: URL) => url.href);
|
||||||
@@ -176,11 +177,11 @@ const URLSelector = ({
|
|||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<Flex mb={15} style={{ width: '100%' }} flexDirection="column">
|
<Flex mb={15} style={{ width: '100%' }} flexDirection="column">
|
||||||
<Txt mb="10px" fontSize="24px">
|
<Txt mb="10px" fontSize="24px">
|
||||||
Use Image URL
|
{i18next.t('source.useSourceURL')}
|
||||||
</Txt>
|
</Txt>
|
||||||
<Input
|
<Input
|
||||||
value={imageURL}
|
value={imageURL}
|
||||||
placeholder="Enter a valid URL"
|
placeholder={i18next.t('source.enterValidURL')}
|
||||||
type="text"
|
type="text"
|
||||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setImageURL(evt.target.value)
|
setImageURL(evt.target.value)
|
||||||
@@ -205,7 +206,7 @@ const URLSelector = ({
|
|||||||
{!showBasicAuth && (
|
{!showBasicAuth && (
|
||||||
<ChevronRightSvg height="1em" fill="currentColor" />
|
<ChevronRightSvg height="1em" fill="currentColor" />
|
||||||
)}
|
)}
|
||||||
<Txt ml={8}>Authentication</Txt>
|
<Txt ml={8}>{i18next.t('source.auth')}</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Link>
|
</Link>
|
||||||
{showBasicAuth && (
|
{showBasicAuth && (
|
||||||
@@ -213,7 +214,7 @@ const URLSelector = ({
|
|||||||
<Input
|
<Input
|
||||||
mb={15}
|
mb={15}
|
||||||
value={username}
|
value={username}
|
||||||
placeholder="Enter username"
|
placeholder={i18next.t('source.username')}
|
||||||
type="text"
|
type="text"
|
||||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setUsername(evt.target.value)
|
setUsername(evt.target.value)
|
||||||
@@ -221,7 +222,7 @@ const URLSelector = ({
|
|||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
value={password}
|
value={password}
|
||||||
placeholder="Enter password"
|
placeholder={i18next.t('source.password')}
|
||||||
type="password"
|
type="password"
|
||||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setPassword(evt.target.value)
|
setPassword(evt.target.value)
|
||||||
@@ -295,7 +296,7 @@ const FlowSelector = styled(
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
color: ${colors.primary.foreground}!important;
|
color: ${colors.primary.foreground} !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -453,7 +454,7 @@ export class SourceSelector extends React.Component<
|
|||||||
!isURL(this.normalizeImagePath(selected))
|
!isURL(this.normalizeImagePath(selected))
|
||||||
) {
|
) {
|
||||||
this.handleError(
|
this.handleError(
|
||||||
'Unsupported protocol',
|
i18next.t('source.unsupportedProtocol'),
|
||||||
selected,
|
selected,
|
||||||
messages.error.unsupportedProtocol(),
|
messages.error.unsupportedProtocol(),
|
||||||
);
|
);
|
||||||
@@ -465,7 +466,7 @@ export class SourceSelector extends React.Component<
|
|||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.looksLikeWindowsImage(),
|
message: messages.warning.looksLikeWindowsImage(),
|
||||||
title: 'Possible Windows image detected',
|
title: i18next.t('source.windowsImage'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -491,13 +492,13 @@ export class SourceSelector extends React.Component<
|
|||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.missingPartitionTable(),
|
message: messages.warning.missingPartitionTable(),
|
||||||
title: 'Missing partition table',
|
title: i18next.t('source.partitionTable'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.handleError(
|
this.handleError(
|
||||||
'Error opening source',
|
i18next.t('source.errorOpen'),
|
||||||
sourcePath,
|
sourcePath,
|
||||||
messages.error.openSource(sourcePath, error.message),
|
messages.error.openSource(sourcePath, error.message),
|
||||||
error,
|
error,
|
||||||
@@ -515,7 +516,7 @@ export class SourceSelector extends React.Component<
|
|||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.driveMissingPartitionTable(),
|
message: messages.warning.driveMissingPartitionTable(),
|
||||||
title: 'Missing partition table',
|
title: i18next.t('source.partitionTable'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -719,7 +720,7 @@ export class SourceSelector extends React.Component<
|
|||||||
mb={14}
|
mb={14}
|
||||||
onClick={() => this.reselectSource()}
|
onClick={() => this.reselectSource()}
|
||||||
>
|
>
|
||||||
Remove
|
{i18next.t('cancel')}
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
)}
|
)}
|
||||||
{!_.isNil(imageSize) && !imageLoading && (
|
{!_.isNil(imageSize) && !imageLoading && (
|
||||||
@@ -734,7 +735,7 @@ export class SourceSelector extends React.Component<
|
|||||||
key="Flash from file"
|
key="Flash from file"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openImageSelector(),
|
onClick: () => this.openImageSelector(),
|
||||||
label: 'Flash from file',
|
label: i18next.t('source.fromFile'),
|
||||||
icon: <FileSvg height="1em" fill="currentColor" />,
|
icon: <FileSvg height="1em" fill="currentColor" />,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||||
@@ -744,7 +745,7 @@ export class SourceSelector extends React.Component<
|
|||||||
key="Flash from URL"
|
key="Flash from URL"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openURLSelector(),
|
onClick: () => this.openURLSelector(),
|
||||||
label: 'Flash from URL',
|
label: i18next.t('source.fromURL'),
|
||||||
icon: <LinkSvg height="1em" fill="currentColor" />,
|
icon: <LinkSvg height="1em" fill="currentColor" />,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||||
@@ -754,7 +755,7 @@ export class SourceSelector extends React.Component<
|
|||||||
key="Clone drive"
|
key="Clone drive"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openDriveSelector(),
|
onClick: () => this.openDriveSelector(),
|
||||||
label: 'Clone drive',
|
label: i18next.t('source.clone'),
|
||||||
icon: <CopySvg height="1em" fill="currentColor" />,
|
icon: <CopySvg height="1em" fill="currentColor" />,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||||
@@ -775,7 +776,7 @@ export class SourceSelector extends React.Component<
|
|||||||
<span>{this.state.warning.title}</span>
|
<span>{this.state.warning.title}</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
action="Continue"
|
action={i18next.t('continue')}
|
||||||
cancel={() => {
|
cancel={() => {
|
||||||
this.setState({ warning: null });
|
this.setState({ warning: null });
|
||||||
this.reselectSource();
|
this.reselectSource();
|
||||||
@@ -793,17 +794,17 @@ export class SourceSelector extends React.Component<
|
|||||||
|
|
||||||
{showImageDetails && (
|
{showImageDetails && (
|
||||||
<SmallModal
|
<SmallModal
|
||||||
title="Image"
|
title={i18next.t('source.image')}
|
||||||
done={() => {
|
done={() => {
|
||||||
this.setState({ showImageDetails: false });
|
this.setState({ showImageDetails: false });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Txt.p>
|
<Txt.p>
|
||||||
<Txt.span bold>Name: </Txt.span>
|
<Txt.span bold>{i18next.t('source.name')}</Txt.span>
|
||||||
<Txt.span>{imageName || imageBasename}</Txt.span>
|
<Txt.span>{imageName || imageBasename}</Txt.span>
|
||||||
</Txt.p>
|
</Txt.p>
|
||||||
<Txt.p>
|
<Txt.p>
|
||||||
<Txt.span bold>Path: </Txt.span>
|
<Txt.span bold>{i18next.t('source.path')}</Txt.span>
|
||||||
<Txt.span>{imagePath}</Txt.span>
|
<Txt.span>{imagePath}</Txt.span>
|
||||||
</Txt.p>
|
</Txt.p>
|
||||||
</SmallModal>
|
</SmallModal>
|
||||||
@@ -842,8 +843,8 @@ export class SourceSelector extends React.Component<
|
|||||||
<DriveSelector
|
<DriveSelector
|
||||||
write={false}
|
write={false}
|
||||||
multipleSelection={false}
|
multipleSelection={false}
|
||||||
titleLabel="Select source"
|
titleLabel={i18next.t('source.selectSource')}
|
||||||
emptyListLabel="Plug a source drive"
|
emptyListLabel={i18next.t('source.plugSource')}
|
||||||
emptyListIcon={<SrcSvg width="40px" />}
|
emptyListIcon={<SrcSvg width="40px" />}
|
||||||
cancel={(originalList) => {
|
cancel={(originalList) => {
|
||||||
if (originalList.length) {
|
if (originalList.length) {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
StepNameButton,
|
StepNameButton,
|
||||||
} from '../../styled-components';
|
} from '../../styled-components';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
interface TargetSelectorProps {
|
interface TargetSelectorProps {
|
||||||
targets: any[];
|
targets: any[];
|
||||||
@@ -95,7 +96,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
|
|||||||
</StepNameButton>
|
</StepNameButton>
|
||||||
{!props.flashing && (
|
{!props.flashing && (
|
||||||
<ChangeButton plain mb={14} onClick={props.reselectDrive}>
|
<ChangeButton plain mb={14} onClick={props.reselectDrive}>
|
||||||
Change
|
{i18next.t('target.change')}
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
)}
|
)}
|
||||||
{target.size != null && (
|
{target.size != null && (
|
||||||
@@ -132,11 +133,11 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StepNameButton plain tooltip={props.tooltip}>
|
<StepNameButton plain tooltip={props.tooltip}>
|
||||||
{targets.length} Targets
|
{targets.length} {i18next.t('target.targets')}
|
||||||
</StepNameButton>
|
</StepNameButton>
|
||||||
{!props.flashing && (
|
{!props.flashing && (
|
||||||
<ChangeButton plain onClick={props.reselectDrive} mb={14}>
|
<ChangeButton plain onClick={props.reselectDrive} mb={14}>
|
||||||
Change
|
{i18next.t('target.change')}
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
)}
|
)}
|
||||||
{targetsTemplate}
|
{targetsTemplate}
|
||||||
@@ -151,7 +152,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
|
|||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
onClick={props.openDriveSelector}
|
onClick={props.openDriveSelector}
|
||||||
>
|
>
|
||||||
Select target
|
{i18next.t('target.selectTarget')}
|
||||||
</StepButton>
|
</StepButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import TgtSvg from '../../../assets/tgt.svg';
|
|||||||
import DriveSvg from '../../../assets/drive.svg';
|
import DriveSvg from '../../../assets/drive.svg';
|
||||||
import { warning } from '../../../../shared/messages';
|
import { warning } from '../../../../shared/messages';
|
||||||
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export const getDriveListLabel = () => {
|
export const getDriveListLabel = () => {
|
||||||
return getSelectedDrives()
|
return getSelectedDrives()
|
||||||
@@ -60,8 +61,8 @@ export const TargetSelectorModal = (
|
|||||||
) => (
|
) => (
|
||||||
<DriveSelector
|
<DriveSelector
|
||||||
multipleSelection={true}
|
multipleSelection={true}
|
||||||
titleLabel="Select target"
|
titleLabel={i18next.t('target.selectTarget')}
|
||||||
emptyListLabel="Plug a target drive"
|
emptyListLabel={i18next.t('target.plugTarget')}
|
||||||
emptyListIcon={<TgtSvg width="40px" />}
|
emptyListIcon={<TgtSvg width="40px" />}
|
||||||
showWarnings={true}
|
showWarnings={true}
|
||||||
selectedList={getSelectedDrives()}
|
selectedList={getSelectedDrives()}
|
||||||
|
|||||||
42
lib/gui/app/i18n.ts
Normal file
42
lib/gui/app/i18n.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import * as i18next from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import zh_CN_translation from './i18n/zh-CN';
|
||||||
|
import zh_TW_translation from './i18n/zh-TW';
|
||||||
|
import en_translation from './i18n/en';
|
||||||
|
|
||||||
|
export function langParser() {
|
||||||
|
if (process.env.LANG !== undefined) {
|
||||||
|
// Bypass mocha, where lang-detect don't works
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
const lang = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||||
|
|
||||||
|
switch (lang.substr(0, 2)) {
|
||||||
|
case 'zh':
|
||||||
|
if (lang === 'zh-CN' || lang === 'zh-SG') {
|
||||||
|
return 'zh-CN';
|
||||||
|
} // Simplified Chinese
|
||||||
|
else {
|
||||||
|
return 'zh-TW';
|
||||||
|
} // Traditional Chinese
|
||||||
|
default:
|
||||||
|
return lang.substr(0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i18next.use(initReactI18next).init({
|
||||||
|
lng: langParser(),
|
||||||
|
fallbackLng: 'en',
|
||||||
|
nonExplicitSupportedLngs: true,
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
'zh-CN': zh_CN_translation,
|
||||||
|
'zh-TW': zh_TW_translation,
|
||||||
|
en: en_translation,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18next;
|
||||||
23
lib/gui/app/i18n/README.md
Normal file
23
lib/gui/app/i18n/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# i18n
|
||||||
|
|
||||||
|
## How it was done
|
||||||
|
|
||||||
|
Using the open-source lib [i18next](https://www.i18next.com/).
|
||||||
|
|
||||||
|
## How to add your own language
|
||||||
|
|
||||||
|
1. Go to `lib/gui/app/i18n` and add a file named `xx.ts` (use the codes mentioned
|
||||||
|
in [the link](https://www.science.co.il/language/Locale-codes.php), and we support styles as `fr`, `de`, `es-ES`
|
||||||
|
and `pt-BR`)
|
||||||
|
.
|
||||||
|
2. Copy the content from an existing translation and start to translate.
|
||||||
|
3. Once done, go to `lib/gui/app/i18n.ts` and add a line of `import xx_translation from './i18n/xx'` after the
|
||||||
|
already-added imports and add `xx: xx_translation` in the `resources` section of `i18next.init()` function.
|
||||||
|
4. Now go to `lib/shared/catalina-sudo/` and copy the `sudo-askpass.osascript-en.js`, change it to
|
||||||
|
be `sudo-askpass.osascript-xx.js` and edit
|
||||||
|
the `'balenaEtcher needs privileged access in order to flash disks.\n\nType your password to allow this.'` line and
|
||||||
|
those `Ok`s and `Cancel`s to your own language.
|
||||||
|
5. If, your language has several variations when they are used in several countries/regions, such as `zh-CN` and `zh-TW`
|
||||||
|
, or `pt-BR` and `pt-PT`, edit
|
||||||
|
the `langParser()` in the `lib/gui/app/i18n.ts` file to meet your need.
|
||||||
|
6. Make a commit, and then a pull request on GitHub.
|
||||||
161
lib/gui/app/i18n/en.ts
Normal file
161
lib/gui/app/i18n/en.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
const translation = {
|
||||||
|
translation: {
|
||||||
|
continue: 'Continue',
|
||||||
|
ok: 'OK',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
skip: 'Skip',
|
||||||
|
sure: "Yes, I'm sure",
|
||||||
|
warning: 'WARNING! ',
|
||||||
|
attention: 'Attention',
|
||||||
|
failed: 'Failed',
|
||||||
|
completed: 'Completed',
|
||||||
|
yesContinue: 'Yes, continue',
|
||||||
|
reallyExit: 'Are you sure you want to close Etcher?',
|
||||||
|
yesExit: 'Yes, quit',
|
||||||
|
progress: {
|
||||||
|
starting: 'Starting...',
|
||||||
|
decompressing: 'Decompressing...',
|
||||||
|
flashing: 'Flashing...',
|
||||||
|
finishing: 'Finishing...',
|
||||||
|
verifying: 'Validating...',
|
||||||
|
failing: 'Failed',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
sizeNotRecommended: 'Not recommended',
|
||||||
|
tooSmall: 'Too small',
|
||||||
|
locked: 'Locked',
|
||||||
|
system: 'System drive',
|
||||||
|
containsImage: 'Source drive',
|
||||||
|
largeDrive: 'Large drive',
|
||||||
|
sourceLarger: 'The selected source is {{byte}} larger than this drive.',
|
||||||
|
flashSucceed_one: 'Successful target',
|
||||||
|
flashSucceed_other: 'Successful targets',
|
||||||
|
flashFail_one: 'Failed target',
|
||||||
|
flashFail_other: 'Failed targets',
|
||||||
|
toDrive: 'to {{description}} ({{name}})',
|
||||||
|
toTarget_one: 'to {{num}} target',
|
||||||
|
toTarget_other: 'to {{num}} targets',
|
||||||
|
andFailTarget_one: 'and failed to be flashed to {{num}} target',
|
||||||
|
andFailTarget_other: 'and failed to be flashed to {{num}} targets',
|
||||||
|
succeedTo: '{{name}} was successfully flashed {{target}}',
|
||||||
|
exitWhileFlashing:
|
||||||
|
'You are currently flashing a drive. Closing Etcher may leave your drive in an unusable state.',
|
||||||
|
looksLikeWindowsImage:
|
||||||
|
'It looks like you are trying to burn a Windows image.\n\nUnlike other images, Windows images require special processing to be made bootable. We suggest you use a tool specially designed for this purpose, such as <a href="https://rufus.akeo.ie">Rufus</a> (Windows), <a href="https://github.com/slacka/WoeUSB">WoeUSB</a> (Linux), or Boot Camp Assistant (macOS).',
|
||||||
|
image: 'image',
|
||||||
|
drive: 'drive',
|
||||||
|
missingPartitionTable:
|
||||||
|
'It looks like this is not a bootable {{type}}.\n\nThe {{type}} does not appear to contain a partition table, and might not be recognized or bootable by your device.',
|
||||||
|
largeDriveSize:
|
||||||
|
"This is a large drive! Make sure it doesn't contain files that you want to keep.",
|
||||||
|
systemDrive:
|
||||||
|
'Selecting your system drive is dangerous and will erase your drive!',
|
||||||
|
sourceDrive: 'Contains the image you chose to flash',
|
||||||
|
noSpace:
|
||||||
|
'Not enough space on the drive. Please insert larger one and try again.',
|
||||||
|
genericFlashError:
|
||||||
|
'Something went wrong. If it is a compressed image, please check that the archive is not corrupted.\n{{error}}',
|
||||||
|
validation:
|
||||||
|
'The write has been completed successfully but Etcher detected potential corruption issues when reading the image back from the drive. \n\nPlease consider writing the image to a different drive.',
|
||||||
|
openError:
|
||||||
|
'Something went wrong while opening {{source}}.\n\nError: {{error}}',
|
||||||
|
flashError: 'Something went wrong while writing {{image}} {{targets}}.',
|
||||||
|
unplug:
|
||||||
|
"Looks like Etcher lost access to the drive. Did it get unplugged accidentally?\n\nSometimes this error is caused by faulty readers that don't provide stable access to the drive.",
|
||||||
|
cannotWrite:
|
||||||
|
'Looks like Etcher is not able to write to this location of the drive. This error is usually caused by a faulty drive, reader, or port. \n\nPlease try again with another drive, reader, or port.',
|
||||||
|
childWriterDied:
|
||||||
|
'The writer process ended unexpectedly. Please try again, and contact the Etcher team if the problem persists.',
|
||||||
|
badProtocol: 'Only http:// and https:// URLs are supported.',
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
selectTarget: 'Select target',
|
||||||
|
plugTarget: 'Plug a target drive',
|
||||||
|
targets: 'Targets',
|
||||||
|
change: 'Change',
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
useSourceURL: 'Use Image URL',
|
||||||
|
auth: 'Authentication',
|
||||||
|
username: 'Enter username',
|
||||||
|
password: 'Enter password',
|
||||||
|
unsupportedProtocol: 'Unsupported protocol',
|
||||||
|
windowsImage: 'Possible Windows image detected',
|
||||||
|
partitionTable: 'Missing partition table',
|
||||||
|
errorOpen: 'Error opening source',
|
||||||
|
fromFile: 'Flash from file',
|
||||||
|
fromURL: 'Flash from URL',
|
||||||
|
clone: 'Clone drive',
|
||||||
|
image: 'Image',
|
||||||
|
name: 'Name: ',
|
||||||
|
path: 'Path: ',
|
||||||
|
selectSource: 'Select source',
|
||||||
|
plugSource: 'Plug a source drive',
|
||||||
|
osImages: 'OS Images',
|
||||||
|
allFiles: 'All',
|
||||||
|
enterValidURL: 'Enter a valid URL',
|
||||||
|
},
|
||||||
|
drives: {
|
||||||
|
name: 'Name',
|
||||||
|
size: 'Size',
|
||||||
|
location: 'Location',
|
||||||
|
find: '{{length}} found',
|
||||||
|
select: 'Select {{select}}',
|
||||||
|
showHidden: 'Show {{num}} hidden',
|
||||||
|
systemDriveDanger:
|
||||||
|
'Selecting your system drive is dangerous and will erase your drive!',
|
||||||
|
openInBrowser: '`Etcher will open {{link}} in your browser`',
|
||||||
|
changeTarget: 'Change target',
|
||||||
|
largeDriveWarning: 'You are about to erase an unusually large drive',
|
||||||
|
largeDriveWarningMsg:
|
||||||
|
'Are you sure the selected drive is not a storage drive?',
|
||||||
|
systemDriveWarning: "You are about to erase your computer's drives",
|
||||||
|
systemDriveWarningMsg:
|
||||||
|
'Are you sure you want to flash your system drive?',
|
||||||
|
},
|
||||||
|
flash: {
|
||||||
|
another: 'Flash another',
|
||||||
|
target: 'Target',
|
||||||
|
location: 'Location',
|
||||||
|
error: 'Error',
|
||||||
|
flash: 'Flash',
|
||||||
|
flashNow: 'Flash!',
|
||||||
|
skip: 'Validation has been skipped',
|
||||||
|
moreInfo: 'more info',
|
||||||
|
speedTip:
|
||||||
|
'The speed is calculated by dividing the image size by the flashing time.\nDisk images with ext partitions flash faster as we are able to skip unused parts.',
|
||||||
|
speed: 'Effective speed: {{speed}} MB/s',
|
||||||
|
speedShort: '{{speed}} MB/s',
|
||||||
|
eta: 'ETA: {{eta}}',
|
||||||
|
failedTarget: 'Failed targets',
|
||||||
|
failedRetry: 'Retry failed targets',
|
||||||
|
flashFailed: 'Flash Failed.',
|
||||||
|
flashCompleted: 'Flash Completed!',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
errorReporting:
|
||||||
|
'Anonymously report errors and usage statistics to balena.io',
|
||||||
|
autoUpdate: 'Auto-updates enabled',
|
||||||
|
settings: 'Settings',
|
||||||
|
systemInformation: 'System Information',
|
||||||
|
trimExtPartitions: 'Trim unallocated space on raw images (in ext-type partitions)',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
edit: 'Edit',
|
||||||
|
view: 'View',
|
||||||
|
devTool: 'Toggle Developer Tools',
|
||||||
|
window: 'Window',
|
||||||
|
help: 'Help',
|
||||||
|
pro: 'Etcher Pro',
|
||||||
|
website: 'Etcher Website',
|
||||||
|
issue: 'Report an issue',
|
||||||
|
about: 'About Etcher',
|
||||||
|
hide: 'Hide Etcher',
|
||||||
|
hideOthers: 'Hide Others',
|
||||||
|
unhide: 'Unhide All',
|
||||||
|
quit: 'Quit Etcher',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translation;
|
||||||
152
lib/gui/app/i18n/zh-CN.ts
Normal file
152
lib/gui/app/i18n/zh-CN.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
const translation = {
|
||||||
|
translation: {
|
||||||
|
ok: '好',
|
||||||
|
cancel: '取消',
|
||||||
|
continue: '继续',
|
||||||
|
skip: '跳过',
|
||||||
|
sure: '我确定',
|
||||||
|
warning: '请注意!',
|
||||||
|
attention: '请注意',
|
||||||
|
failed: '失败',
|
||||||
|
completed: '完毕',
|
||||||
|
yesExit: '是的,可以退出',
|
||||||
|
reallyExit: '真的要现在退出 Etcher 吗?',
|
||||||
|
yesContinue: '是的,继续',
|
||||||
|
progress: {
|
||||||
|
starting: '正在启动……',
|
||||||
|
decompressing: '正在解压……',
|
||||||
|
flashing: '正在烧录……',
|
||||||
|
finishing: '正在结束……',
|
||||||
|
verifying: '正在验证……',
|
||||||
|
failing: '失败……',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
sizeNotRecommended: '大小不推荐',
|
||||||
|
tooSmall: '空间太小',
|
||||||
|
locked: '被锁定',
|
||||||
|
system: '系统盘',
|
||||||
|
containsImage: '存放源镜像',
|
||||||
|
largeDrive: '很大的磁盘',
|
||||||
|
sourceLarger: '所选的镜像比目标盘大了 {{byte}} 比特。',
|
||||||
|
flashSucceed_one: '烧录成功',
|
||||||
|
flashSucceed_other: '烧录成功',
|
||||||
|
flashFail_one: '烧录失败',
|
||||||
|
flashFail_other: '烧录失败',
|
||||||
|
toDrive: '到 {{description}} ({{name}})',
|
||||||
|
toTarget_one: '到 {{num}} 个目标',
|
||||||
|
toTarget_other: '到 {{num}} 个目标',
|
||||||
|
andFailTarget_one: '并烧录失败了 {{num}} 个目标',
|
||||||
|
andFailTarget_other: '并烧录失败了 {{num}} 个目标',
|
||||||
|
succeedTo: '{{name}} 被成功烧录 {{target}}',
|
||||||
|
exitWhileFlashing:
|
||||||
|
'您当前正在刷机。 关闭 Etcher 可能会导致您的磁盘无法使用。',
|
||||||
|
looksLikeWindowsImage:
|
||||||
|
'看起来您正在尝试刻录 Windows 镜像。\n\n与其他镜像不同,Windows 镜像需要特殊处理才能使其可启动。 我们建议您使用专门为此目的设计的工具,例如 <a href="https://rufus.akeo.ie">Rufus</a> (Windows)、<a href="https://github. com/slacka/WoeUSB">WoeUSB</a> (Linux) 或 Boot Camp 助理 (macOS)。',
|
||||||
|
image: '镜像',
|
||||||
|
drive: '磁盘',
|
||||||
|
missingPartitionTable:
|
||||||
|
'看起来这不是一个可启动的{{type}}。\n\n这个{{type}}似乎不包含分区表,因此您的设备可能无法识别或无法正确启动。',
|
||||||
|
largeDriveSize: '这是个很大的磁盘!请检查并确认它不包含对您很重要的信息',
|
||||||
|
systemDrive: '选择系统盘很危险,因为这将会删除你的系统',
|
||||||
|
sourceDrive: '源镜像位于这个分区中',
|
||||||
|
noSpace: '磁盘空间不足。 请插入另一个较大的磁盘并重试。',
|
||||||
|
genericFlashError:
|
||||||
|
'出了点问题。如果源镜像曾被压缩过,请检查它是否已损坏。\n{{error}}',
|
||||||
|
validation:
|
||||||
|
'写入已成功完成,但 Etcher 在从磁盘读取镜像时检测到潜在的损坏问题。 \n\n请考虑将镜像写入其他磁盘。',
|
||||||
|
openError: '打开 {{source}} 时出错。\n\n错误信息: {{error}}',
|
||||||
|
flashError: '烧录 {{image}} {{targets}} 失败。',
|
||||||
|
unplug:
|
||||||
|
'看起来 Etcher 失去了对磁盘的连接。 它是不是被意外拔掉了?\n\n有时这个错误是因为读卡器出了故障。',
|
||||||
|
cannotWrite:
|
||||||
|
'看起来 Etcher 无法写入磁盘的这个位置。 此错误通常是由故障的磁盘、读取器或端口引起的。 \n\n请使用其他磁盘、读卡器或端口重试。',
|
||||||
|
childWriterDied:
|
||||||
|
'写入进程意外崩溃。请再试一次,如果问题仍然存在,请联系 Etcher 团队。',
|
||||||
|
badProtocol: '仅支持 http:// 和 https:// 开头的网址。',
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
selectTarget: '选择目标磁盘',
|
||||||
|
plugTarget: '请插入目标磁盘',
|
||||||
|
targets: '个目标',
|
||||||
|
change: '更改',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
edit: '编辑',
|
||||||
|
view: '视图',
|
||||||
|
devTool: '打开开发者工具',
|
||||||
|
window: '窗口',
|
||||||
|
help: '帮助',
|
||||||
|
pro: 'Etcher 专业版',
|
||||||
|
website: 'Etcher 的官网',
|
||||||
|
issue: '提交一个 issue',
|
||||||
|
about: '关于 Etcher',
|
||||||
|
hide: '隐藏 Etcher',
|
||||||
|
hideOthers: '隐藏其它窗口',
|
||||||
|
unhide: '取消隐藏',
|
||||||
|
quit: '退出 Etcher',
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
useSourceURL: '使用镜像网络地址',
|
||||||
|
auth: '验证',
|
||||||
|
username: '输入用户名',
|
||||||
|
password: '输入密码',
|
||||||
|
unsupportedProtocol: '不支持的协议',
|
||||||
|
windowsImage: '这可能是 Windows 系统镜像',
|
||||||
|
partitionTable: '找不到分区表',
|
||||||
|
errorOpen: '打开源镜像时出错',
|
||||||
|
fromFile: '从文件烧录',
|
||||||
|
fromURL: '从在线地址烧录',
|
||||||
|
clone: '克隆磁盘',
|
||||||
|
image: '镜像信息',
|
||||||
|
name: '名称:',
|
||||||
|
path: '路径:',
|
||||||
|
selectSource: '选择源',
|
||||||
|
plugSource: '请插入源磁盘',
|
||||||
|
osImages: '系统镜像格式',
|
||||||
|
allFiles: '任何文件格式',
|
||||||
|
enterValidURL: '请输入一个正确的地址',
|
||||||
|
},
|
||||||
|
drives: {
|
||||||
|
name: '名称',
|
||||||
|
size: '大小',
|
||||||
|
location: '位置',
|
||||||
|
find: '找到 {{length}} 个',
|
||||||
|
select: '选定 {{select}}',
|
||||||
|
showHidden: '显示 {{num}} 个隐藏的磁盘',
|
||||||
|
systemDriveDanger: '选择系统盘很危险,因为这将会删除你的系统!',
|
||||||
|
openInBrowser: 'Etcher 会在浏览器中打开 {{link}}',
|
||||||
|
changeTarget: '改变目标',
|
||||||
|
largeDriveWarning: '您即将擦除一个非常大的磁盘',
|
||||||
|
largeDriveWarningMsg: '您确定所选磁盘不是存储磁盘吗?',
|
||||||
|
systemDriveWarning: '您将要擦除系统盘',
|
||||||
|
systemDriveWarningMsg: '您确定要烧录到系统盘吗?',
|
||||||
|
},
|
||||||
|
flash: {
|
||||||
|
another: '烧录另一目标',
|
||||||
|
target: '目标',
|
||||||
|
location: '位置',
|
||||||
|
error: '错误',
|
||||||
|
flash: '烧录',
|
||||||
|
flashNow: '现在烧录!',
|
||||||
|
skip: '跳过了验证',
|
||||||
|
moreInfo: '更多信息',
|
||||||
|
speedTip:
|
||||||
|
'通过将镜像大小除以烧录时间来计算速度。\n由于我们能够跳过未使用的部分,因此具有EXT分区的磁盘镜像烧录速度更快。',
|
||||||
|
speed: '速度:{{speed}} MB/秒',
|
||||||
|
speedShort: '{{speed}} MB/秒',
|
||||||
|
eta: '预计还需要:{{eta}}',
|
||||||
|
failedTarget: '失败的烧录目标',
|
||||||
|
failedRetry: '重试烧录失败目标',
|
||||||
|
flashFailed: '烧录失败。',
|
||||||
|
flashCompleted: '烧录成功!',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
errorReporting: '匿名地向 balena.io 报告运行错误和使用统计',
|
||||||
|
autoUpdate: '自动更新',
|
||||||
|
settings: '软件设置',
|
||||||
|
systemInformation: '系统信息',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translation;
|
||||||
152
lib/gui/app/i18n/zh-TW.ts
Normal file
152
lib/gui/app/i18n/zh-TW.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
const translation = {
|
||||||
|
translation: {
|
||||||
|
ok: '好',
|
||||||
|
cancel: '取消',
|
||||||
|
continue: '繼續',
|
||||||
|
skip: '跳過',
|
||||||
|
sure: '我確定',
|
||||||
|
warning: '請注意!',
|
||||||
|
attention: '請注意',
|
||||||
|
failed: '失敗',
|
||||||
|
completed: '完畢',
|
||||||
|
yesExit: '是的,可以退出',
|
||||||
|
reallyExit: '真的要現在退出 Etcher 嗎?',
|
||||||
|
yesContinue: '是的,繼續',
|
||||||
|
progress: {
|
||||||
|
starting: '正在啓動……',
|
||||||
|
decompressing: '正在解壓……',
|
||||||
|
flashing: '正在燒錄……',
|
||||||
|
finishing: '正在結束……',
|
||||||
|
verifying: '正在驗證……',
|
||||||
|
failing: '失敗……',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
sizeNotRecommended: '大小不推薦',
|
||||||
|
tooSmall: '空間太小',
|
||||||
|
locked: '被鎖定',
|
||||||
|
system: '系統盤',
|
||||||
|
containsImage: '存放源鏡像',
|
||||||
|
largeDrive: '很大的磁盤',
|
||||||
|
sourceLarger: '所選的鏡像比目標盤大了 {{byte}} 比特。',
|
||||||
|
flashSucceed_one: '燒錄成功',
|
||||||
|
flashSucceed_other: '燒錄成功',
|
||||||
|
flashFail_one: '燒錄失敗',
|
||||||
|
flashFail_other: '燒錄失敗',
|
||||||
|
toDrive: '到 {{description}} ({{name}})',
|
||||||
|
toTarget_one: '到 {{num}} 個目標',
|
||||||
|
toTarget_other: '到 {{num}} 個目標',
|
||||||
|
andFailTarget_one: '並燒錄失敗了 {{num}} 個目標',
|
||||||
|
andFailTarget_other: '並燒錄失敗了 {{num}} 個目標',
|
||||||
|
succeedTo: '{{name}} 被成功燒錄 {{target}}',
|
||||||
|
exitWhileFlashing:
|
||||||
|
'您當前正在刷機。 關閉 Etcher 可能會導致您的磁盤無法使用。',
|
||||||
|
looksLikeWindowsImage:
|
||||||
|
'看起來您正在嘗試刻錄 Windows 鏡像。\n\n與其他鏡像不同,Windows 鏡像需要特殊處理才能使其可啓動。 我們建議您使用專門爲此目的設計的工具,例如 <a href="https://rufus.akeo.ie">Rufus</a> (Windows)、<a href="https://github. com/slacka/WoeUSB">WoeUSB</a> (Linux) 或 Boot Camp 助理 (macOS)。',
|
||||||
|
image: '鏡像',
|
||||||
|
drive: '磁盤',
|
||||||
|
missingPartitionTable:
|
||||||
|
'看起來這不是一個可啓動的{{type}}。\n\n這個{{type}}似乎不包含分區表,因此您的設備可能無法識別或無法正確啓動。',
|
||||||
|
largeDriveSize: '這是個很大的磁盤!請檢查並確認它不包含對您很重要的信息',
|
||||||
|
systemDrive: '選擇系統盤很危險,因爲這將會刪除你的系統',
|
||||||
|
sourceDrive: '源鏡像位於這個分區中',
|
||||||
|
noSpace: '磁盤空間不足。 請插入另一個較大的磁盤並重試。',
|
||||||
|
genericFlashError:
|
||||||
|
'出了點問題。如果源鏡像曾被壓縮過,請檢查它是否已損壞。\n{{error}}',
|
||||||
|
validation:
|
||||||
|
'寫入已成功完成,但 Etcher 在從磁盤讀取鏡像時檢測到潛在的損壞問題。 \n\n請考慮將鏡像寫入其他磁盤。',
|
||||||
|
openError: '打開 {{source}} 時出錯。\n\n錯誤信息: {{error}}',
|
||||||
|
flashError: '燒錄 {{image}} {{targets}} 失敗。',
|
||||||
|
unplug:
|
||||||
|
'看起來 Etcher 失去了對磁盤的連接。 它是不是被意外拔掉了?\n\n有時這個錯誤是因爲讀卡器出了故障。',
|
||||||
|
cannotWrite:
|
||||||
|
'看起來 Etcher 無法寫入磁盤的這個位置。 此錯誤通常是由故障的磁盤、讀取器或端口引起的。 \n\n請使用其他磁盤、讀卡器或端口重試。',
|
||||||
|
childWriterDied:
|
||||||
|
'寫入進程意外崩潰。請再試一次,如果問題仍然存在,請聯繫 Etcher 團隊。',
|
||||||
|
badProtocol: '僅支持 http:// 和 https:// 開頭的網址。',
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
selectTarget: '選擇目標磁盤',
|
||||||
|
plugTarget: '請插入目標磁盤',
|
||||||
|
targets: '個目標',
|
||||||
|
change: '更改',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
edit: '編輯',
|
||||||
|
view: '視圖',
|
||||||
|
devTool: '打開開發者工具',
|
||||||
|
window: '窗口',
|
||||||
|
help: '幫助',
|
||||||
|
pro: 'Etcher 專業版',
|
||||||
|
website: 'Etcher 的官網',
|
||||||
|
issue: '提交一個 issue',
|
||||||
|
about: '關於 Etcher',
|
||||||
|
hide: '隱藏 Etcher',
|
||||||
|
hideOthers: '隱藏其它窗口',
|
||||||
|
unhide: '取消隱藏',
|
||||||
|
quit: '退出 Etcher',
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
useSourceURL: '使用鏡像網絡地址',
|
||||||
|
auth: '驗證',
|
||||||
|
username: '輸入用戶名',
|
||||||
|
password: '輸入密碼',
|
||||||
|
unsupportedProtocol: '不支持的協議',
|
||||||
|
windowsImage: '這可能是 Windows 系統鏡像',
|
||||||
|
partitionTable: '找不到分區表',
|
||||||
|
errorOpen: '打開源鏡像時出錯',
|
||||||
|
fromFile: '從文件燒錄',
|
||||||
|
fromURL: '從在線地址燒錄',
|
||||||
|
clone: '克隆磁盤',
|
||||||
|
image: '鏡像信息',
|
||||||
|
name: '名稱:',
|
||||||
|
path: '路徑:',
|
||||||
|
selectSource: '選擇源',
|
||||||
|
plugSource: '請插入源磁盤',
|
||||||
|
osImages: '系統鏡像格式',
|
||||||
|
allFiles: '任何文件格式',
|
||||||
|
enterValidURL: '請輸入一個正確的地址',
|
||||||
|
},
|
||||||
|
drives: {
|
||||||
|
name: '名稱',
|
||||||
|
size: '大小',
|
||||||
|
location: '位置',
|
||||||
|
find: '找到 {{length}} 個',
|
||||||
|
select: '選定 {{select}}',
|
||||||
|
showHidden: '顯示 {{num}} 個隱藏的磁盤',
|
||||||
|
systemDriveDanger: '選擇系統盤很危險,因爲這將會刪除你的系統!',
|
||||||
|
openInBrowser: 'Etcher 會在瀏覽器中打開 {{link}}',
|
||||||
|
changeTarget: '改變目標',
|
||||||
|
largeDriveWarning: '您即將擦除一個非常大的磁盤',
|
||||||
|
largeDriveWarningMsg: '您確定所選磁盤不是存儲磁盤嗎?',
|
||||||
|
systemDriveWarning: '您將要擦除系統盤',
|
||||||
|
systemDriveWarningMsg: '您確定要燒錄到系統盤嗎?',
|
||||||
|
},
|
||||||
|
flash: {
|
||||||
|
another: '燒錄另一目標',
|
||||||
|
target: '目標',
|
||||||
|
location: '位置',
|
||||||
|
error: '錯誤',
|
||||||
|
flash: '燒錄',
|
||||||
|
flashNow: '現在燒錄!',
|
||||||
|
skip: '跳過了驗證',
|
||||||
|
moreInfo: '更多信息',
|
||||||
|
speedTip:
|
||||||
|
'通過將鏡像大小除以燒錄時間來計算速度。\n由於我們能夠跳過未使用的部分,因此具有EXT分區的磁盤鏡像燒錄速度更快。',
|
||||||
|
speed: '速度:{{speed}} MB/秒',
|
||||||
|
speedShort: '{{speed}} MB/秒',
|
||||||
|
eta: '預計還需要:{{eta}}',
|
||||||
|
failedTarget: '失敗的燒錄目標',
|
||||||
|
failedRetry: '重試燒錄失敗目標',
|
||||||
|
flashFailed: '燒錄失敗。',
|
||||||
|
flashCompleted: '燒錄成功!',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
errorReporting: '匿名地向 balena.io 報告運行錯誤和使用統計',
|
||||||
|
autoUpdate: '自動更新',
|
||||||
|
settings: '軟件設置',
|
||||||
|
systemInformation: '系統信息',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translation;
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export interface FlashState {
|
export interface FlashState {
|
||||||
active: number;
|
active: number;
|
||||||
@@ -34,36 +35,45 @@ export function fromFlashState({
|
|||||||
position?: string;
|
position?: string;
|
||||||
} {
|
} {
|
||||||
if (type === undefined) {
|
if (type === undefined) {
|
||||||
return { status: 'Starting...' };
|
return { status: i18next.t('progress.starting') };
|
||||||
} else if (type === 'decompressing') {
|
} else if (type === 'decompressing') {
|
||||||
if (percentage == null) {
|
if (percentage == null) {
|
||||||
return { status: 'Decompressing...' };
|
return { status: i18next.t('progress.decompressing') };
|
||||||
} else {
|
} else {
|
||||||
return { position: `${percentage}%`, status: 'Decompressing...' };
|
return {
|
||||||
|
position: `${percentage}%`,
|
||||||
|
status: i18next.t('progress.decompressing'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else if (type === 'flashing') {
|
} else if (type === 'flashing') {
|
||||||
if (percentage != null) {
|
if (percentage != null) {
|
||||||
if (percentage < 100) {
|
if (percentage < 100) {
|
||||||
return { position: `${percentage}%`, status: 'Flashing...' };
|
return {
|
||||||
|
position: `${percentage}%`,
|
||||||
|
status: i18next.t('progress.flashing'),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return { status: 'Finishing...' };
|
return { status: i18next.t('progress.finishing') };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: 'Flashing...',
|
status: i18next.t('progress.flashing'),
|
||||||
position: `${position ? prettyBytes(position) : ''}`,
|
position: `${position ? prettyBytes(position) : ''}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (type === 'verifying') {
|
} else if (type === 'verifying') {
|
||||||
if (percentage == null) {
|
if (percentage == null) {
|
||||||
return { status: 'Validating...' };
|
return { status: i18next.t('progress.verifying') };
|
||||||
} else if (percentage < 100) {
|
} else if (percentage < 100) {
|
||||||
return { position: `${percentage}%`, status: 'Validating...' };
|
return {
|
||||||
|
position: `${percentage}%`,
|
||||||
|
status: i18next.t('progress.verifying'),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return { status: 'Finishing...' };
|
return { status: i18next.t('progress.finishing') };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { status: 'Failed' };
|
return { status: i18next.t('progress.failing') };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function titleFromFlashState(
|
export function titleFromFlashState(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import * as _ from 'lodash';
|
|||||||
import * as errors from '../../../shared/errors';
|
import * as errors from '../../../shared/errors';
|
||||||
import * as settings from '../../../gui/app/models/settings';
|
import * as settings from '../../../gui/app/models/settings';
|
||||||
import { SUPPORTED_EXTENSIONS } from '../../../shared/supported-formats';
|
import { SUPPORTED_EXTENSIONS } from '../../../shared/supported-formats';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
async function mountSourceDrive() {
|
async function mountSourceDrive() {
|
||||||
// sourceDrivePath is the name of the link in /dev/disk/by-path
|
// sourceDrivePath is the name of the link in /dev/disk/by-path
|
||||||
@@ -53,11 +54,11 @@ export async function selectImage(): Promise<string | undefined> {
|
|||||||
properties: ['openFile', 'treatPackageAsDirectory'],
|
properties: ['openFile', 'treatPackageAsDirectory'],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: 'OS Images',
|
name: i18next.t('source.osImages'),
|
||||||
extensions: SUPPORTED_EXTENSIONS,
|
extensions: SUPPORTED_EXTENSIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'All',
|
name: i18next.t('source.allFiles'),
|
||||||
extensions: ['*'],
|
extensions: ['*'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -79,8 +80,8 @@ export async function showWarning(options: {
|
|||||||
description: string;
|
description: string;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
_.defaults(options, {
|
_.defaults(options, {
|
||||||
confirmationLabel: 'OK',
|
confirmationLabel: i18next.t('ok'),
|
||||||
rejectionLabel: 'Cancel',
|
rejectionLabel: i18next.t('cancel'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const BUTTONS = [options.confirmationLabel, options.rejectionLabel];
|
const BUTTONS = [options.confirmationLabel, options.rejectionLabel];
|
||||||
@@ -98,7 +99,7 @@ export async function showWarning(options: {
|
|||||||
buttons: BUTTONS,
|
buttons: BUTTONS,
|
||||||
defaultId: BUTTON_REJECTION_INDEX,
|
defaultId: BUTTON_REJECTION_INDEX,
|
||||||
cancelId: BUTTON_REJECTION_INDEX,
|
cancelId: BUTTON_REJECTION_INDEX,
|
||||||
title: 'Attention',
|
title: i18next.t('attention'),
|
||||||
message: options.title,
|
message: options.title,
|
||||||
detail: options.description,
|
detail: options.description,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
|
|
||||||
import FlashSvg from '../../../assets/flash.svg';
|
import FlashSvg from '../../../assets/flash.svg';
|
||||||
import DriveStatusWarningModal from '../../components/drive-status-warning-modal/drive-status-warning-modal';
|
import DriveStatusWarningModal from '../../components/drive-status-warning-modal/drive-status-warning-modal';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const COMPLETED_PERCENTAGE = 100;
|
const COMPLETED_PERCENTAGE = 100;
|
||||||
const SPEED_PRECISION = 2;
|
const SPEED_PRECISION = 2;
|
||||||
@@ -293,9 +294,17 @@ export class FlashStep extends React.PureComponent<
|
|||||||
color="#7e8085"
|
color="#7e8085"
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Txt>{this.props.speed.toFixed(SPEED_PRECISION)} MB/s</Txt>
|
<Txt>
|
||||||
|
{i18next.t('flash.speedShort', {
|
||||||
|
speed: this.props.speed.toFixed(SPEED_PRECISION),
|
||||||
|
})}
|
||||||
|
</Txt>
|
||||||
{!_.isNil(this.props.eta) && (
|
{!_.isNil(this.props.eta) && (
|
||||||
<Txt>ETA: {formatSeconds(this.props.eta)}</Txt>
|
<Txt>
|
||||||
|
{i18next.t('flash.eta', {
|
||||||
|
eta: formatSeconds(this.props.eta),
|
||||||
|
})}
|
||||||
|
</Txt>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { main } from './app';
|
import { main } from './app';
|
||||||
|
import './i18n';
|
||||||
|
import { langParser } from './i18n';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
ipcRenderer.send('change-lng', langParser());
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept('./app', () => {
|
module.hot.accept('./app', () => {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export const Modal = styled(({ style, children, ...props }) => {
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollableFlex flexDirection="column" width="100%" height="90%">
|
<ScrollableFlex flexDirection="column" width="100%" height="90%">
|
||||||
{...children}
|
{children.length ? children.map((c: any) => <>{c}</>) : children}
|
||||||
</ScrollableFlex>
|
</ScrollableFlex>
|
||||||
</ModalBase>
|
</ModalBase>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,18 +21,22 @@ import { platform } from 'os';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
|
import './app/i18n';
|
||||||
|
|
||||||
import { packageType, version } from '../../package.json';
|
import { packageType, version } from '../../package.json';
|
||||||
import * as EXIT_CODES from '../shared/exit-codes';
|
import * as EXIT_CODES from '../shared/exit-codes';
|
||||||
import { delay, getConfig } from '../shared/utils';
|
import { delay, getConfig } from '../shared/utils';
|
||||||
import * as settings from './app/models/settings';
|
import * as settings from './app/models/settings';
|
||||||
import { logException } from './app/modules/analytics';
|
import { logException } from './app/modules/analytics';
|
||||||
import { buildWindowMenu } from './menu';
|
import { buildWindowMenu } from './menu';
|
||||||
|
import * as i18n from 'i18next';
|
||||||
|
|
||||||
const customProtocol = 'etcher';
|
const customProtocol = 'etcher';
|
||||||
const scheme = `${customProtocol}://`;
|
const scheme = `${customProtocol}://`;
|
||||||
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
||||||
const packageUpdatable = updatablePackageTypes.includes(packageType);
|
const packageUpdatable = updatablePackageTypes.includes(packageType);
|
||||||
let packageUpdated = false;
|
let packageUpdated = false;
|
||||||
|
let mainWindow: any = null;
|
||||||
|
|
||||||
async function checkForUpdates(interval: number) {
|
async function checkForUpdates(interval: number) {
|
||||||
// We use a while loop instead of a setInterval to preserve
|
// We use a while loop instead of a setInterval to preserve
|
||||||
@@ -130,7 +134,7 @@ async function createMainWindow() {
|
|||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
({ width, height } = electron.screen.getPrimaryDisplay().bounds);
|
({ width, height } = electron.screen.getPrimaryDisplay().bounds);
|
||||||
}
|
}
|
||||||
const mainWindow = new electron.BrowserWindow({
|
mainWindow = new electron.BrowserWindow({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frame: !fullscreen,
|
frame: !fullscreen,
|
||||||
@@ -157,7 +161,6 @@ async function createMainWindow() {
|
|||||||
|
|
||||||
electron.app.setAsDefaultProtocolClient(customProtocol);
|
electron.app.setAsDefaultProtocolClient(customProtocol);
|
||||||
|
|
||||||
buildWindowMenu(mainWindow);
|
|
||||||
mainWindow.setFullScreen(true);
|
mainWindow.setFullScreen(true);
|
||||||
|
|
||||||
// Prevent flash of white when starting the application
|
// Prevent flash of white when starting the application
|
||||||
@@ -240,6 +243,17 @@ async function main(): Promise<void> {
|
|||||||
await selectImageURL(await getCommandLineURL(argv));
|
await selectImageURL(await getCommandLineURL(argv));
|
||||||
});
|
});
|
||||||
await selectImageURL(await getCommandLineURL(process.argv));
|
await selectImageURL(await getCommandLineURL(process.argv));
|
||||||
|
|
||||||
|
electron.ipcMain.on('change-lng', function (event, args) {
|
||||||
|
i18n.changeLanguage(args, () => {
|
||||||
|
console.log('Language changed to: ' + args);
|
||||||
|
});
|
||||||
|
if (mainWindow != null) {
|
||||||
|
buildWindowMenu(mainWindow);
|
||||||
|
} else {
|
||||||
|
console.log('Build menu failed. ');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import { displayName } from '../../package.json';
|
import { displayName } from '../../package.json';
|
||||||
|
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Builds a native application menu for a given window
|
* @summary Builds a native application menu for a given window
|
||||||
*/
|
*/
|
||||||
@@ -42,12 +44,13 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
const menuTemplate: electron.MenuItemConstructorOptions[] = [
|
const menuTemplate: electron.MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
role: 'editMenu',
|
role: 'editMenu',
|
||||||
|
label: i18next.t('menu.edit'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'View',
|
label: i18next.t('menu.view'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Toggle Developer Tools',
|
label: i18next.t('menu.devTool'),
|
||||||
accelerator:
|
accelerator:
|
||||||
process.platform === 'darwin' ? 'Command+Alt+I' : 'Control+Shift+I',
|
process.platform === 'darwin' ? 'Command+Alt+I' : 'Control+Shift+I',
|
||||||
click: toggleDevTools,
|
click: toggleDevTools,
|
||||||
@@ -56,12 +59,14 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'windowMenu',
|
role: 'windowMenu',
|
||||||
|
label: i18next.t('menu.window'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'help',
|
role: 'help',
|
||||||
|
label: i18next.t('menu.help'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Etcher Pro',
|
label: i18next.t('menu.pro'),
|
||||||
click() {
|
click() {
|
||||||
electron.shell.openExternal(
|
electron.shell.openExternal(
|
||||||
'https://etcher.io/pro?utm_source=etcher_menu&ref=etcher_menu',
|
'https://etcher.io/pro?utm_source=etcher_menu&ref=etcher_menu',
|
||||||
@@ -69,13 +74,13 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Etcher Website',
|
label: i18next.t('menu.website'),
|
||||||
click() {
|
click() {
|
||||||
electron.shell.openExternal('https://etcher.io?ref=etcher_menu');
|
electron.shell.openExternal('https://etcher.io?ref=etcher_menu');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Report an issue',
|
label: i18next.t('menu.issue'),
|
||||||
click() {
|
click() {
|
||||||
electron.shell.openExternal(
|
electron.shell.openExternal(
|
||||||
'https://github.com/balena-io/etcher/issues',
|
'https://github.com/balena-io/etcher/issues',
|
||||||
@@ -92,25 +97,29 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
role: 'about' as const,
|
role: 'about' as const,
|
||||||
label: 'About Etcher',
|
label: i18next.t('menu.about'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator' as const,
|
type: 'separator' as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'hide' as const,
|
role: 'hide' as const,
|
||||||
|
label: i18next.t('menu.hide'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'hideOthers' as const,
|
role: 'hideOthers' as const,
|
||||||
|
label: i18next.t('menu.hideOthers'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'unhide' as const,
|
role: 'unhide' as const,
|
||||||
|
label: i18next.t('menu.unhide'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator' as const,
|
type: 'separator' as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'quit' as const,
|
role: 'quit' as const,
|
||||||
|
label: i18next.t('menu.quit'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
21
lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
Executable file
21
lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env osascript -l JavaScript
|
||||||
|
|
||||||
|
ObjC.import('stdlib')
|
||||||
|
|
||||||
|
const app = Application.currentApplication()
|
||||||
|
app.includeStandardAdditions = true
|
||||||
|
|
||||||
|
const result = app.displayDialog('balenaEtcher 需要来自管理员的权限才能烧录镜像到磁盘。\n\n输入您的密码以允许此操作。', {
|
||||||
|
defaultAnswer: '',
|
||||||
|
withIcon: 'caution',
|
||||||
|
buttons: ['取消', '好'],
|
||||||
|
defaultButton: '好',
|
||||||
|
hiddenAnswer: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.buttonReturned === '好') {
|
||||||
|
result.textReturned
|
||||||
|
} else {
|
||||||
|
$.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -30,6 +30,9 @@ export async function sudo(
|
|||||||
command: string,
|
command: string,
|
||||||
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
|
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
|
||||||
try {
|
try {
|
||||||
|
let lang = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||||
|
lang = lang.substr(0, 2);
|
||||||
|
|
||||||
const { stdout, stderr } = await execFileAsync(
|
const { stdout, stderr } = await execFileAsync(
|
||||||
'sudo',
|
'sudo',
|
||||||
['--askpass', 'sh', '-c', `echo ${SUCCESSFUL_AUTH_MARKER} && ${command}`],
|
['--askpass', 'sh', '-c', `echo ${SUCCESSFUL_AUTH_MARKER} && ${command}`],
|
||||||
@@ -40,7 +43,7 @@ export async function sudo(
|
|||||||
SUDO_ASKPASS: join(
|
SUDO_ASKPASS: join(
|
||||||
getAppPath(),
|
getAppPath(),
|
||||||
__dirname,
|
__dirname,
|
||||||
'sudo-askpass.osascript.js',
|
'sudo-askpass.osascript-' + lang + '.js',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
import { Dictionary } from 'lodash';
|
import { Dictionary } from 'lodash';
|
||||||
import { outdent } from 'outdent';
|
import { outdent } from 'outdent';
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
|
import '../gui/app/i18n';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export const progress: Dictionary<(quantity: number) => string> = {
|
export const progress: Dictionary<(quantity: number) => string> = {
|
||||||
successful: (quantity: number) => {
|
successful: (quantity: number) => {
|
||||||
const plural = quantity === 1 ? '' : 's';
|
return i18next.t('message.flashSucceed', { count: quantity });
|
||||||
return `Successful target${plural}`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
failed: (quantity: number) => {
|
failed: (quantity: number) => {
|
||||||
const plural = quantity === 1 ? '' : 's';
|
return i18next.t('message.flashFail', { count: quantity });
|
||||||
return `Failed target${plural}`;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,129 +38,121 @@ export const info = {
|
|||||||
) => {
|
) => {
|
||||||
const targets = [];
|
const targets = [];
|
||||||
if (failed + successful === 1) {
|
if (failed + successful === 1) {
|
||||||
targets.push(`to ${drive.description} (${drive.displayName})`);
|
targets.push(
|
||||||
|
i18next.t('message.toDrive', {
|
||||||
|
description: drive.description,
|
||||||
|
name: drive.displayName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (successful) {
|
if (successful) {
|
||||||
const plural = successful === 1 ? '' : 's';
|
targets.push(
|
||||||
targets.push(`to ${successful} target${plural}`);
|
i18next.t('message.toTarget', {
|
||||||
|
count: successful,
|
||||||
|
num: successful,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (failed) {
|
if (failed) {
|
||||||
const plural = failed === 1 ? '' : 's';
|
targets.push(
|
||||||
targets.push(`and failed to be flashed to ${failed} target${plural}`);
|
i18next.t('message.andFailTarget', { count: failed, num: failed }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${imageBasename} was successfully flashed ${targets.join(' ')}`;
|
return i18next.t('message.succeedTo', {
|
||||||
|
name: imageBasename,
|
||||||
|
target: targets.join(' '),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const compatibility = {
|
export const compatibility = {
|
||||||
sizeNotRecommended: () => {
|
sizeNotRecommended: () => {
|
||||||
return 'Not recommended';
|
return i18next.t('message.sizeNotRecommended');
|
||||||
},
|
},
|
||||||
|
|
||||||
tooSmall: () => {
|
tooSmall: () => {
|
||||||
return 'Too small';
|
return i18next.t('message.tooSmall');
|
||||||
},
|
},
|
||||||
|
|
||||||
locked: () => {
|
locked: () => {
|
||||||
return 'Locked';
|
return i18next.t('message.locked');
|
||||||
},
|
},
|
||||||
|
|
||||||
system: () => {
|
system: () => {
|
||||||
return 'System drive';
|
return i18next.t('message.system');
|
||||||
},
|
},
|
||||||
|
|
||||||
containsImage: () => {
|
containsImage: () => {
|
||||||
return 'Source drive';
|
return i18next.t('message.containsImage');
|
||||||
},
|
},
|
||||||
|
|
||||||
// The drive is large and therefore likely not a medium you want to write to.
|
// The drive is large and therefore likely not a medium you want to write to.
|
||||||
largeDrive: () => {
|
largeDrive: () => {
|
||||||
return 'Large drive';
|
return i18next.t('message.largeDrive');
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const warning = {
|
export const warning = {
|
||||||
tooSmall: (source: { size: number }, target: { size: number }) => {
|
tooSmall: (source: { size: number }, target: { size: number }) => {
|
||||||
return outdent({ newline: ' ' })`
|
return outdent({ newline: ' ' })`
|
||||||
The selected source is ${prettyBytes(source.size - target.size)}
|
${i18next.t('message.sourceLarger', {
|
||||||
larger than this drive.
|
byte: prettyBytes(source.size - target.size),
|
||||||
|
})}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
exitWhileFlashing: () => {
|
exitWhileFlashing: () => {
|
||||||
return [
|
return i18next.t('message.exitWhileFlashing');
|
||||||
'You are currently flashing a drive.',
|
|
||||||
'Closing Etcher may leave your drive in an unusable state.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
looksLikeWindowsImage: () => {
|
looksLikeWindowsImage: () => {
|
||||||
return [
|
return i18next.t('message.looksLikeWindowsImage');
|
||||||
'It looks like you are trying to burn a Windows image.\n\n',
|
|
||||||
'Unlike other images, Windows images require special processing to be made bootable.',
|
|
||||||
'We suggest you use a tool specially designed for this purpose, such as',
|
|
||||||
'<a href="https://rufus.akeo.ie">Rufus</a> (Windows),',
|
|
||||||
'<a href="https://github.com/slacka/WoeUSB">WoeUSB</a> (Linux),',
|
|
||||||
'or Boot Camp Assistant (macOS).',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
missingPartitionTable: () => {
|
missingPartitionTable: () => {
|
||||||
return [
|
return i18next.t('message.missingPartitionTable', {
|
||||||
'It looks like this is not a bootable image.\n\n',
|
type: i18next.t('message.image'),
|
||||||
'The image does not appear to contain a partition table,',
|
});
|
||||||
'and might not be recognized or bootable by your device.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
driveMissingPartitionTable: () => {
|
driveMissingPartitionTable: () => {
|
||||||
return outdent({ newline: ' ' })`
|
return i18next.t('message.missingPartitionTable', {
|
||||||
It looks like this is not a bootable drive.
|
type: i18next.t('message.drive'),
|
||||||
The drive does not appear to contain a partition table,
|
});
|
||||||
and might not be recognized or bootable by your device.
|
|
||||||
`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
largeDriveSize: () => {
|
largeDriveSize: () => {
|
||||||
return "This is a large drive! Make sure it doesn't contain files that you want to keep.";
|
return i18next.t('message.largeDriveSize');
|
||||||
},
|
},
|
||||||
|
|
||||||
systemDrive: () => {
|
systemDrive: () => {
|
||||||
return 'Selecting your system drive is dangerous and will erase your drive!';
|
return i18next.t('message.systemDrive');
|
||||||
},
|
},
|
||||||
|
|
||||||
sourceDrive: () => {
|
sourceDrive: () => {
|
||||||
return 'Contains the image you chose to flash';
|
return i18next.t('message.sourceDrive');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const error = {
|
export const error = {
|
||||||
notEnoughSpaceInDrive: () => {
|
notEnoughSpaceInDrive: () => {
|
||||||
return [
|
return i18next.t('message.noSpace');
|
||||||
'Not enough space on the drive.',
|
|
||||||
'Please insert larger one and try again.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
genericFlashError: (err: Error) => {
|
genericFlashError: (err: Error) => {
|
||||||
return `Something went wrong. If it is a compressed image, please check that the archive is not corrupted.\n${err.message}`;
|
return i18next.t('message.genericFlashError', { error: err.message });
|
||||||
},
|
},
|
||||||
|
|
||||||
validation: () => {
|
validation: () => {
|
||||||
return [
|
return i18next.t('message.validation');
|
||||||
'The write has been completed successfully but Etcher detected potential',
|
|
||||||
'corruption issues when reading the image back from the drive.',
|
|
||||||
'\n\nPlease consider writing the image to a different drive.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
openSource: (sourceName: string, errorMessage: string) => {
|
openSource: (sourceName: string, errorMessage: string) => {
|
||||||
return outdent`
|
return i18next.t('message.openError', {
|
||||||
Something went wrong while opening ${sourceName}
|
source: sourceName,
|
||||||
|
error: errorMessage,
|
||||||
Error: ${errorMessage}
|
});
|
||||||
`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
flashFailure: (
|
flashFailure: (
|
||||||
@@ -169,35 +161,33 @@ export const error = {
|
|||||||
) => {
|
) => {
|
||||||
const target =
|
const target =
|
||||||
drives.length === 1
|
drives.length === 1
|
||||||
? `${drives[0].description} (${drives[0].displayName})`
|
? i18next.t('message.toDrive', {
|
||||||
: `${drives.length} targets`;
|
description: drives[0].description,
|
||||||
return `Something went wrong while writing ${imageBasename} to ${target}.`;
|
name: drives[0].displayName,
|
||||||
|
})
|
||||||
|
: i18next.t('message.toTarget', {
|
||||||
|
count: drives.length,
|
||||||
|
num: drives.length,
|
||||||
|
});
|
||||||
|
return i18next.t('message.flashError', {
|
||||||
|
image: imageBasename,
|
||||||
|
targets: target,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
driveUnplugged: () => {
|
driveUnplugged: () => {
|
||||||
return [
|
return i18next.t('message.unplug');
|
||||||
'Looks like Etcher lost access to the drive.',
|
|
||||||
'Did it get unplugged accidentally?',
|
|
||||||
"\n\nSometimes this error is caused by faulty readers that don't provide stable access to the drive.",
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
inputOutput: () => {
|
inputOutput: () => {
|
||||||
return [
|
return i18next.t('message.cannotWrite');
|
||||||
'Looks like Etcher is not able to write to this location of the drive.',
|
|
||||||
'This error is usually caused by a faulty drive, reader, or port.',
|
|
||||||
'\n\nPlease try again with another drive, reader, or port.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
childWriterDied: () => {
|
childWriterDied: () => {
|
||||||
return [
|
return i18next.t('message.childWriterDied');
|
||||||
'The writer process ended unexpectedly.',
|
|
||||||
'Please try again, and contact the Etcher team if the problem persists.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unsupportedProtocol: () => {
|
unsupportedProtocol: () => {
|
||||||
return 'Only http:// and https:// URLs are supported.';
|
return i18next.t('message.badProtocol');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
2859
package-lock.json
generated
2859
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "balena-etcher",
|
"name": "balena-etcher",
|
||||||
"private": true,
|
"private": true,
|
||||||
"displayName": "balenaEtcher",
|
"displayName": "balenaEtcher",
|
||||||
"version": "1.10.24",
|
"version": "1.13.2",
|
||||||
"packageType": "local",
|
"packageType": "local",
|
||||||
"main": "generated/etcher.js",
|
"main": "generated/etcher.js",
|
||||||
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
||||||
@@ -64,8 +64,8 @@
|
|||||||
"@types/react": "16.14.34",
|
"@types/react": "16.14.34",
|
||||||
"@types/react-dom": "16.9.17",
|
"@types/react-dom": "16.9.17",
|
||||||
"@types/semver": "7.3.13",
|
"@types/semver": "7.3.13",
|
||||||
"@types/sinon": "9.0.0",
|
"@types/sinon": "9.0.11",
|
||||||
"@types/terser-webpack-plugin": "5.0.2",
|
"@types/terser-webpack-plugin": "5.0.4",
|
||||||
"@types/tmp": "0.2.3",
|
"@types/tmp": "0.2.3",
|
||||||
"@types/webpack-node-externals": "2.5.3",
|
"@types/webpack-node-externals": "2.5.3",
|
||||||
"aws4-axios": "2.4.9",
|
"aws4-axios": "2.4.9",
|
||||||
@@ -74,16 +74,17 @@
|
|||||||
"css-loader": "5.2.7",
|
"css-loader": "5.2.7",
|
||||||
"d3": "4.13.0",
|
"d3": "4.13.0",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"electron": "12.2.3",
|
"electron": "^13.5.0",
|
||||||
"electron-builder": "22.14.13",
|
"electron-builder": "^23.0.9",
|
||||||
"electron-mocha": "9.3.3",
|
"electron-mocha": "9.3.3",
|
||||||
"electron-notarize": "1.2.2",
|
"electron-notarize": "1.2.2",
|
||||||
"electron-rebuild": "3.2.9",
|
"electron-rebuild": "3.2.9",
|
||||||
"electron-updater": "4.6.5",
|
"electron-updater": "5.3.0",
|
||||||
"esbuild-loader": "2.20.0",
|
"esbuild-loader": "2.20.0",
|
||||||
"etcher-sdk": "7.4.0",
|
"etcher-sdk": "^7.4.6",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"husky": "4.3.8",
|
"husky": "4.3.8",
|
||||||
|
"i18next": "21.10.0",
|
||||||
"immutable": "3.8.2",
|
"immutable": "3.8.2",
|
||||||
"lint-staged": "10.5.4",
|
"lint-staged": "10.5.4",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
@@ -94,36 +95,37 @@
|
|||||||
"omit-deep-lodash": "1.1.7",
|
"omit-deep-lodash": "1.1.7",
|
||||||
"outdent": "0.8.0",
|
"outdent": "0.8.0",
|
||||||
"path-is-inside": "1.0.2",
|
"path-is-inside": "1.0.2",
|
||||||
"pnp-webpack-plugin": "1.6.4",
|
"pnp-webpack-plugin": "1.7.0",
|
||||||
"pretty-bytes": "5.3.0",
|
"pretty-bytes": "5.6.0",
|
||||||
"react": "16.8.5",
|
"react": "16.8.5",
|
||||||
"react-dom": "16.8.5",
|
"react-dom": "16.8.5",
|
||||||
"redux": "4.0.5",
|
"react-i18next": "11.18.6",
|
||||||
"rendition": "19.2.0",
|
"redux": "4.2.0",
|
||||||
|
"rendition": "19.3.2",
|
||||||
"resin-corvus": "2.0.5",
|
"resin-corvus": "2.0.5",
|
||||||
"semver": "7.3.8",
|
"semver": "7.3.8",
|
||||||
"simple-progress-webpack-plugin": "1.1.2",
|
"simple-progress-webpack-plugin": "1.1.2",
|
||||||
"sinon": "9.0.2",
|
"sinon": "9.2.4",
|
||||||
"spectron": "14.0.0",
|
"spectron": "15.0.0",
|
||||||
"string-replace-loader": "3.0.1",
|
"string-replace-loader": "3.1.0",
|
||||||
"style-loader": "2.0.0",
|
"style-loader": "2.0.0",
|
||||||
"styled-components": "5.1.0",
|
"styled-components": "5.3.6",
|
||||||
"sys-class-rgb-led": "3.0.1",
|
"sys-class-rgb-led": "3.0.1",
|
||||||
"terser-webpack-plugin": "5.2.5",
|
"terser-webpack-plugin": "5.3.6",
|
||||||
"ts-loader": "8.0.12",
|
"ts-loader": "8.4.0",
|
||||||
"ts-node": "9.1.1",
|
"ts-node": "9.1.1",
|
||||||
"tslib": "2.0.0",
|
"tslib": "2.4.1",
|
||||||
"typescript": "4.4.4",
|
"typescript": "4.4.4",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"uuid": "8.1.0",
|
"uuid": "8.3.2",
|
||||||
"webpack": "5.11.0",
|
"webpack": "5.75.0",
|
||||||
"webpack-cli": "4.2.0",
|
"webpack-cli": "4.10.0",
|
||||||
"webpack-dev-server": "4.5.0"
|
"webpack-dev-server": "4.11.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14 < 16"
|
"node": ">=14 < 16"
|
||||||
},
|
},
|
||||||
"versionist": {
|
"versionist": {
|
||||||
"publishedAt": "2022-12-09T14:59:55.579Z"
|
"publishedAt": "2023-01-02T20:55:58.804Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
awscli==1.27.26
|
awscli==1.27.28
|
||||||
shyaml==0.5.0
|
shyaml==0.6.2
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as CopyPlugin from 'copy-webpack-plugin';
|
import * as CopyPlugin from 'copy-webpack-plugin';
|
||||||
import { readdirSync } from 'fs';
|
import { readdirSync, existsSync } from 'fs';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import outdent from 'outdent';
|
import outdent from 'outdent';
|
||||||
@@ -77,25 +77,45 @@ function renameNodeModules(resourcePath: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findExt2fsFolder(): string {
|
||||||
|
const ext2fs = 'node_modules/ext2fs';
|
||||||
|
const biFsExt2fs = 'node_modules/balena-image-fs/node_modules/ext2fs';
|
||||||
|
|
||||||
|
if (existsSync(ext2fs)) {
|
||||||
|
return ext2fs;
|
||||||
|
} else if (existsSync(biFsExt2fs)) {
|
||||||
|
return biFsExt2fs;
|
||||||
|
} else {
|
||||||
|
throw Error('ext2fs not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeExt2FsRegex(): RegExp {
|
||||||
|
const folder = findExt2fsFolder();
|
||||||
|
const libpath = '/lib/libext2fs\\.js$';
|
||||||
|
|
||||||
|
return new RegExp(folder.concat(libpath));
|
||||||
|
}
|
||||||
|
|
||||||
function findUsbPrebuild(): string[] {
|
function findUsbPrebuild(): string[] {
|
||||||
const usbPrebuildsFolder = path.join('node_modules', 'usb', 'prebuilds')
|
const usbPrebuildsFolder = path.join('node_modules', 'usb', 'prebuilds');
|
||||||
const prebuildFolders = readdirSync(usbPrebuildsFolder);
|
const prebuildFolders = readdirSync(usbPrebuildsFolder);
|
||||||
let bindingFile: string | undefined = 'node.napi.node';
|
let bindingFile: string | undefined = 'node.napi.node';
|
||||||
const platformFolder = prebuildFolders.find(
|
const platformFolder = prebuildFolders.find(
|
||||||
(f) =>
|
(f) => f.startsWith(os.platform()) && f.indexOf(os.arch()) > -1,
|
||||||
f.startsWith(os.platform()) &&
|
|
||||||
f.indexOf(os.arch()) > -1,
|
|
||||||
);
|
);
|
||||||
if (platformFolder === undefined) {
|
if (platformFolder === undefined) {
|
||||||
throw new Error('Could not find usb prebuild. Should try fallback to node-gyp and use /build/Release instead of /prebuilds');
|
throw new Error(
|
||||||
|
'Could not find usb prebuild. Should try fallback to node-gyp and use /build/Release instead of /prebuilds',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bindingFiles = readdirSync(
|
const bindingFiles = readdirSync(
|
||||||
path.join(usbPrebuildsFolder, platformFolder)
|
path.join(usbPrebuildsFolder, platformFolder),
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!bindingFiles.length) {
|
if (!bindingFiles.length) {
|
||||||
throw new Error('Could not find usb prebuild for platform')
|
throw new Error('Could not find usb prebuild for platform');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindingFiles.length === 1) {
|
if (bindingFiles.length === 1) {
|
||||||
@@ -107,25 +127,22 @@ function findUsbPrebuild(): string[] {
|
|||||||
if (bindingFiles.length > 1) {
|
if (bindingFiles.length > 1) {
|
||||||
bindingFile = bindingFiles.find((file) => {
|
bindingFile = bindingFiles.find((file) => {
|
||||||
if (bindingFiles.indexOf('arm') > -1) {
|
if (bindingFiles.indexOf('arm') > -1) {
|
||||||
const process = require('process')
|
const process = require('process');
|
||||||
return file.indexOf(process.config.variables.arm_version) > -1
|
return file.indexOf(process.config.variables.arm_version) > -1;
|
||||||
} else {
|
} else {
|
||||||
return file.indexOf('glibc') > -1
|
return file.indexOf('glibc') > -1;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindingFile === undefined) {
|
if (bindingFile === undefined) {
|
||||||
throw new Error('Could not find usb prebuild for platform')
|
throw new Error('Could not find usb prebuild for platform');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [platformFolder, bindingFile];
|
return [platformFolder, bindingFile];
|
||||||
}
|
}
|
||||||
|
|
||||||
const [
|
const [USB_BINDINGS_FOLDER, USB_BINDINGS_FILE] = findUsbPrebuild();
|
||||||
USB_BINDINGS_FOLDER,
|
|
||||||
USB_BINDINGS_FILE,
|
|
||||||
] = findUsbPrebuild();
|
|
||||||
|
|
||||||
function findLzmaNativeBindingsFolder(): string {
|
function findLzmaNativeBindingsFolder(): string {
|
||||||
const files = readdirSync(
|
const files = readdirSync(
|
||||||
@@ -325,8 +342,8 @@ const commonConfig = {
|
|||||||
// Use the libext2fs.wasm file in the generated folder
|
// Use the libext2fs.wasm file in the generated folder
|
||||||
// The way to find the app directory depends on whether we run in the renderer or in the child-writer
|
// The way to find the app directory depends on whether we run in the renderer or in the child-writer
|
||||||
// We use __dirname in the child-writer and electron.remote.app.getAppPath() in the renderer
|
// We use __dirname in the child-writer and electron.remote.app.getAppPath() in the renderer
|
||||||
replace(/node_modules\/ext2fs\/lib\/libext2fs\.js$/, {
|
replace(makeExt2FsRegex(), {
|
||||||
search: 'scriptDirectory=__dirname+"/"',
|
search: 'scriptDirectory = __dirname + "/";',
|
||||||
replace: fetchWasm('ext2fs', 'lib'),
|
replace: fetchWasm('ext2fs', 'lib'),
|
||||||
}),
|
}),
|
||||||
// Same for node-crc-utils
|
// Same for node-crc-utils
|
||||||
@@ -388,7 +405,7 @@ const guiConfigCopyPatterns = [
|
|||||||
to: 'modules/node-raspberrypi-usbboot/blobs',
|
to: 'modules/node-raspberrypi-usbboot/blobs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: 'node_modules/ext2fs/lib/libext2fs.wasm',
|
from: `${findExt2fsFolder()}/lib/libext2fs.wasm`,
|
||||||
to: 'modules/ext2fs/lib/libext2fs.wasm',
|
to: 'modules/ext2fs/lib/libext2fs.wasm',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user