From db1bf7e4882bc3f1eaaaf9b7bbf247539be527d8 Mon Sep 17 00:00:00 2001 From: r-q Date: Tue, 19 Jul 2022 23:33:42 +0800 Subject: [PATCH 01/10] feat: make i18n and add Chinese support - make i18n using i18next - add Chinese (Simplified) support --- electron-builder.yml | 3 +- lib/gui/app/app.ts | 7 +- .../drive-selector/drive-selector.tsx | 27 +- .../drive-status-warning-modal.tsx | 15 +- .../flash-another/flash-another.tsx | 3 +- .../flash-results/flash-results.tsx | 25 +- .../progress-button/progress-button.tsx | 7 +- lib/gui/app/components/settings/settings.tsx | 10 +- .../source-selector/source-selector.tsx | 45 +-- .../target-selector-button.tsx | 9 +- .../target-selector/target-selector.tsx | 5 +- lib/gui/app/i18n.ts | 318 ++++++++++++++++++ lib/gui/app/modules/progress-status.ts | 30 +- lib/gui/app/os/dialog.ts | 11 +- lib/gui/app/renderer.ts | 6 + lib/gui/etcher.ts | 18 +- lib/gui/menu.ts | 21 +- ...script.js => sudo-askpass.osascript-en.js} | 0 .../sudo-askpass.osascript-zh.js | 21 ++ lib/shared/catalina-sudo/sudo.ts | 5 +- lib/shared/messages.ts | 127 +++---- package-lock.json | 54 ++- package.json | 1 + 23 files changed, 597 insertions(+), 171 deletions(-) create mode 100644 lib/gui/app/i18n.ts rename lib/shared/catalina-sudo/{sudo-askpass.osascript.js => sudo-askpass.osascript-en.js} (100%) create mode 100755 lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js diff --git a/electron-builder.yml b/electron-builder.yml index dd7af17a5b..be9699050a 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -7,7 +7,8 @@ afterSign: ./afterSignHook.js asar: false files: - 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: icon: assets/icon.icns category: public.app-category.developer-tools diff --git a/lib/gui/app/app.ts b/lib/gui/app/app.ts index 979b0879e2..5fbf957475 100644 --- a/lib/gui/app/app.ts +++ b/lib/gui/app/app.ts @@ -38,6 +38,7 @@ import * as osDialog from './os/dialog'; import * as windowProgress from './os/window-progress'; import MainPage from './pages/main/MainPage'; import './css/main.css'; +import i18next from 'i18next'; window.addEventListener( 'unhandledrejection', @@ -313,9 +314,9 @@ window.addEventListener('beforeunload', async (event) => { try { const confirmed = await osDialog.showWarning({ - confirmationLabel: 'Yes, quit', - rejectionLabel: 'Cancel', - title: 'Are you sure you want to close Etcher?', + confirmationLabel: i18next.t('yesExit'), + rejectionLabel: i18next.t('cancel'), + title: i18next.t('reallyExit'), description: messages.warning.exitWhileFlashing(), }); if (confirmed) { diff --git a/lib/gui/app/components/drive-selector/drive-selector.tsx b/lib/gui/app/components/drive-selector/drive-selector.tsx index 4088271ada..a4d25cfafc 100644 --- a/lib/gui/app/components/drive-selector/drive-selector.tsx +++ b/lib/gui/app/components/drive-selector/drive-selector.tsx @@ -44,6 +44,7 @@ import { import { SourceMetadata } from '../source-selector/source-selector'; import { middleEllipsis } from '../../utils/middle-ellipsis'; +import i18next from 'i18next'; interface UsbbootDrive extends sourceDestination.UsbbootDrive { progress: number; @@ -189,7 +190,7 @@ export class DriveSelector extends React.Component< this.tableColumns = [ { field: 'description', - label: 'Name', + label: i18next.t('drives.name'), render: (description: string, drive: Drive) => { if (isDrivelistDrive(drive)) { const isLargeDrive = isDriveSizeLarge(drive); @@ -215,7 +216,7 @@ export class DriveSelector extends React.Component< { field: 'description', key: 'size', - label: 'Size', + label: i18next.t('drives.size'), render: (_description: string, drive: Drive) => { if (isDrivelistDrive(drive) && drive.size !== null) { return prettyBytes(drive.size); @@ -225,7 +226,7 @@ export class DriveSelector extends React.Component< { field: 'description', key: 'link', - label: 'Location', + label: i18next.t('drives.location'), render: (_description: string, drive: Drive) => { return ( @@ -399,14 +400,14 @@ export class DriveSelector extends React.Component< color="#5b82a7" style={{ fontWeight: 600 }} > - {drives.length} found + {i18next.t('drives.find', { length: drives.length })} } titleDetails={{getDrives().length} found} cancel={() => cancel(this.originalList)} done={() => done(selectedList)} - action={`Select (${selectedList.length})`} + action={i18next.t('drives.select', { select: selectedList.length })} primaryButtonProps={{ primary: !showWarnings, warning: showWarnings, @@ -512,7 +513,11 @@ export class DriveSelector extends React.Component< > - Show {numberOfHiddenSystemDrives} hidden + + {i18next.t('drives.showHidden', { + num: numberOfHiddenSystemDrives, + })} + )} @@ -520,7 +525,7 @@ export class DriveSelector extends React.Component< )} {this.props.showWarnings && hasSystemDrives ? ( - Selecting your system drive is dangerous and will erase your drive! + {i18next.t('drives.systemDriveDanger')} ) : null} @@ -540,13 +545,15 @@ export class DriveSelector extends React.Component< this.setState({ missingDriversModal: {} }); } }} - action="Yes, continue" + action={i18next.t('yesContinue')} cancelButtonProps={{ - children: 'Cancel', + children: i18next.t('cancel'), }} children={ missingDriversModal.drive.linkMessage || - `Etcher will open ${missingDriversModal.drive.link} in your browser` + i18next.t('drives.openInBrowser', { + link: missingDriversModal.drive.link, + }) } /> )} diff --git a/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx b/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx index 4000917e50..2f246da554 100644 --- a/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx +++ b/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx @@ -7,6 +7,7 @@ import { middleEllipsis } from '../../utils/middle-ellipsis'; import * as prettyBytes from 'pretty-bytes'; import { DriveWithWarnings } from '../../pages/main/Flash'; +import i18next from 'i18next'; const DriveStatusWarningModal = ({ done, @@ -17,12 +18,12 @@ const DriveStatusWarningModal = ({ isSystem: boolean; drivesWithWarnings: DriveWithWarnings[]; }) => { - let warningSubtitle = 'You are about to erase an unusually large drive'; - let warningCta = 'Are you sure the selected drive is not a storage drive?'; + let warningSubtitle = i18next.t('drives.largeDriveWarning'); + let warningCta = i18next.t('drives.largeDriveWarningMsg'); if (isSystem) { - warningSubtitle = "You are about to erase your computer's drives"; - warningCta = 'Are you sure you want to flash your system drive?'; + warningSubtitle = i18next.t('drives.systemDriveWarning'); + warningCta = i18next.t('drives.systemDriveWarningMsg'); } return ( - WARNING! + {i18next.t('warning')} {warningSubtitle} diff --git a/lib/gui/app/components/flash-another/flash-another.tsx b/lib/gui/app/components/flash-another/flash-another.tsx index 3b5741a374..a0cfd43ca2 100644 --- a/lib/gui/app/components/flash-another/flash-another.tsx +++ b/lib/gui/app/components/flash-another/flash-another.tsx @@ -17,6 +17,7 @@ import * as React from 'react'; import { BaseButton } from '../../styled-components'; +import i18next from 'i18next'; export interface FlashAnotherProps { onClick: () => void; @@ -25,7 +26,7 @@ export interface FlashAnotherProps { export const FlashAnother = (props: FlashAnotherProps) => { return ( - Flash another + {i18next.t('flash.another')} ); }; diff --git a/lib/gui/app/components/flash-results/flash-results.tsx b/lib/gui/app/components/flash-results/flash-results.tsx index 05de2b3d7d..51c8a03281 100644 --- a/lib/gui/app/components/flash-results/flash-results.tsx +++ b/lib/gui/app/components/flash-results/flash-results.tsx @@ -31,6 +31,7 @@ import { resetState } from '../../models/flash-state'; import * as selection from '../../models/selection-state'; import { middleEllipsis } from '../../utils/middle-ellipsis'; import { Modal, Table } from '../../styled-components'; +import i18next from 'i18next'; const ErrorsTable = styled((props) => {...props} />)` &&& [data-display='table-head'], @@ -88,15 +89,15 @@ function formattedErrors(errors: FlashError[]) { const columns: Array> = [ { field: 'description', - label: 'Target', + label: i18next.t('flash.target'), }, { field: 'device', - label: 'Location', + label: i18next.t('flash.location'), }, { field: 'message', - label: 'Error', + label: i18next.t('flash.error'), render: (message: string, { code }: FlashError) => { return message ?? code; }, @@ -162,9 +163,10 @@ export function FlashResults({ {middleEllipsis(image, 24)} - Flash {allFailed ? 'Failed' : 'Complete'}! + {i18next.t('flash.flash')}{' '} + {allFailed ? i18next.t('failed') : i18next.t('completed')}! - {skip ? Validation has been skipped : null} + {skip ? {i18next.t('flash.skip')} : null} {results.devices.successful !== 0 ? ( @@ -188,7 +190,7 @@ export function FlashResults({ {progress.failed(errors.length)} setShowErrorsInfo(true)}> - more info + {i18next.t('flash.moreInfo')} ) : null} @@ -199,12 +201,9 @@ export function FlashResults({ fontWeight: 500, textAlign: 'center', }} - tooltip={outdent({ newline: ' ' })` - 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. - `} + tooltip={i18next.t('flash.speedTip')} > - Effective speed: {effectiveSpeed} MB/s + {i18next.t('flash.speed')} {effectiveSpeed} MB/s )} @@ -214,11 +213,11 @@ export function FlashResults({ titleElement={ - Failed targets + {i18next.t('failedTarget')} } - action="Retry failed targets" + action={i18next.t('failedRetry')} cancel={() => setShowErrorsInfo(false)} done={() => { setShowErrorsInfo(false); diff --git a/lib/gui/app/components/progress-button/progress-button.tsx b/lib/gui/app/components/progress-button/progress-button.tsx index 43fe70e0fa..c84dfb0dbf 100644 --- a/lib/gui/app/components/progress-button/progress-button.tsx +++ b/lib/gui/app/components/progress-button/progress-button.tsx @@ -20,6 +20,7 @@ import { default as styled } from 'styled-components'; import { fromFlashState } from '../../modules/progress-status'; import { StepButton } from '../../styled-components'; +import i18next from 'i18next'; const FlashProgressBar = styled(ProgressBar)` > div { @@ -28,6 +29,7 @@ const FlashProgressBar = styled(ProgressBar)` color: white !important; text-shadow: none !important; transition-duration: 0s; + > div { transition-duration: 0s; } @@ -61,7 +63,7 @@ const colors = { } as const; const CancelButton = styled(({ type, onClick, ...props }) => { - const status = type === 'verifying' ? 'Skip' : 'Cancel'; + const status = type === 'verifying' ? i18next.t('skip') : i18next.t('cancel'); return (