Skip to content

Commit

Permalink
Merge pull request #1502 from nextstrain/feat/i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-aksamentov authored Jul 3, 2024
2 parents d90bd66 + 9d276e5 commit dcbc3b4
Show file tree
Hide file tree
Showing 147 changed files with 38,330 additions and 879 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
\.env
\.idea
\.ignore
\.json-autotranslate-cache
\.out
\.reports
\.Spotlight-V100
Expand Down
51 changes: 51 additions & 0 deletions docs/dev/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,57 @@ Install Node.js version 14+ (latest LTS release is recommended), by either downl
The `yarn prod:serve` command runs Express underneath and it is just an example of a simple (also slow and insecure) local file web server. But the produced HTML, CSS and JS files can be served using any static file web server or static file hosting service. The official deployment uses AWS S3 + Cloudfront.
### Internationalization (translation)
Nextclade Web is using `react-i18n` for internationalization. It is configured in [`packages/nextclade-web/src/i18n`](https://github.com/nextstrain/nextclade/tree/master/packages/nextclade-web/src/i18n). Note that parts of Auspice used in Nextclade are configured separately, but in the same directory.
The actual translations are in [`packages/nextclade-web/src/i18n/resources/`](https://github.com/nextstrain/nextclade/tree/master/packages/nextclade-web/src/i18n/resources).
For machine translation we use [`json-autotranslate`](https://www.npmjs.com/package/json-autotranslate), configured
in [`packages/nextclade-web/json-autotranslate.json`](https://github.com/nextstrain/nextclade/blob/master/packages/nextclade-web/json-autotranslate.json). It stores cache of strings in [`packages/nextclade-web/.json-autotranslate-cache/`](https://github.com/nextstrain/nextclade/blob/master/packages/nextclade-web/.json-autotranslate-cache).
#### Update machine translations
Use this script to extract strings apply machine translations:
```bash
# Extract English strings from the code of Nextclade Web.
# The result will be in `packages/nextclade-web/src/i18n/resources/en/`.
yarn i18n
# Deduplicate, correct, sort and otherwise 'massage' the extracted strings.
yarn i18n:fix
# Translate strings from English to all languages using json-autotranslate.
# Cached strings will be copied as is from cache. If a string is not present in cache,
# it will be machine translated using AWS Translate.
# This step requires AWS credentials (see json-autotranslate docs and ask your AWS admin).
i18n:translate
# 'Massage' the newly translated strings again.
yarn i18n:fix
```
#### Apply manual corrections
If you want to override machine translation, then edit the cached strings in [`packages/nextclade-web/.json-autotranslate-cache/`](https://github.com/nextstrain/nextclade/blob/master/packages/nextclade-web/.json-autotranslate-cache) and submit your changes in a pull request. Developers will check your changes and integrate them into the web app, by running:
```bash
# Deduplicate, correct, sort and otherwise 'massage' the extracted strings.
yarn i18n:fix
# Translate strings from English to all languages using json-autotranslate.
# Cached strings will be copied as is from cache. If a string is not present in cache,
# it will be machine translated using AWS Translate.
# This step requires AWS credentials (see json-autotranslate docs and ask your AWS admin).
i18n:translate
# 'Massage' the newly translated strings again.
yarn i18n:fix
```
Note that dev team does not necessarily understand all supported languages, so it cannot verify quality of either machine or human translations for most languages, except a few.
### Linting (static analysis)
#### Linting Rust code
Expand Down
488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/af/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/am/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ar/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/az/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/bg/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/bn/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/bs/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ca/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/cs/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/cy/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/da/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/de/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/el/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/en/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/es/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/et/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/fa/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/fi/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/fr/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ga/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/gu/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ha/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/he/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/hi/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/hr/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ht/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/hu/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/hy/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/id/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/is/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/it/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ja/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ka/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/kk/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/kn/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ko/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/lt/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/lv/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/mk/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ml/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/mn/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/mr/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ms/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/mt/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/nl/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/no/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/pa/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/pl/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ps/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/pt/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ro/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ru/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/si/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/sk/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/sl/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/so/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/sq/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/sr/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/sv/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/sw/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ta/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/te/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/th/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/tl/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/tr/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/uk/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/ur/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/uz/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/vi/common.json

Large diffs are not rendered by default.

488 changes: 488 additions & 0 deletions packages/nextclade-web/.json-autotranslate-cache/zh/common.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
DropdownMenu as DropdownMenuBase,
DropdownItem as DropdownItemBase,
DropdownProps,
UncontrolledAlert,
} from 'reactstrap'
import { useRecoilState } from 'recoil'
import { SearchBox } from 'src/components/Common/SearchBox'
import { LinkExternal } from 'src/components/Link/LinkExternal'
import { search } from 'src/helpers/search'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import styled from 'styled-components'
Expand Down Expand Up @@ -45,6 +47,19 @@ export function LanguageSwitcher({ ...restProps }: LanguageSwitcherProps) {
<SearchBoxWrapper>
<SearchBox searchTitle={t('Search languages')} searchTerm={searchTerm} onSearchTermChange={setSearchTerm} />
</SearchBoxWrapper>
<UncontrolledAlert
closeClassName="d-none"
fade={false}
color="warning"
className="m-0 mb-2 mr-3 py-1 px-2 small"
>
{t(
'All text except English is generated using machine translation. Accuracy is not guaranteed. Please submit your fixes {{ here }}',
)}
<LinkExternal href="https://github.com/nextstrain/nextclade/blob/master/docs/dev/developer-guide.md#internationalization-translation">
{t('here')}
</LinkExternal>
</UncontrolledAlert>
<DropdownMenuListWrapper>
{localesFiltered.map((locale) => {
const isCurrent = locale.key === currentLocale
Expand Down Expand Up @@ -115,7 +130,7 @@ const DropdownMenu = styled(DropdownMenuBase)`
transition: opacity ease-out 0.25s;
padding: 1rem;
padding-right: 0;
width: 275px;
width: 320px;
`

const DropdownItem = styled(DropdownItemBase)`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import classnames from 'classnames'
import React, { PropsWithChildren, useCallback, useState } from 'react'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import styled from 'styled-components'
import {
Nav as NavBase,
Expand All @@ -16,24 +17,25 @@ import { DatasetContentTabAdvanced } from 'src/components/Main/DatasetContentTab
import { DatasetCustomizationIndicator } from 'src/components/Main/DatasetCustomizationIndicator'

export function DatasetContentSection() {
const { t } = useTranslationSafe()
const [activeTabId, setActiveTabId] = useState(0)
const currentDataset = useRecoilValue(datasetCurrentAtom)
return (
<ContentSection>
<Nav tabs>
{currentDataset?.files?.readme && (
<TabLabel tabId={0} activeTabId={activeTabId} setActiveTabId={setActiveTabId}>
{'Summary'}
{t('Summary')}
</TabLabel>
)}
{currentDataset?.files?.changelog && (
<TabLabel tabId={1} activeTabId={activeTabId} setActiveTabId={setActiveTabId}>
{'History'}
{t('History')}
</TabLabel>
)}
{currentDataset && (
<TabLabel tabId={2} activeTabId={activeTabId} setActiveTabId={setActiveTabId}>
{'Customize'}
{t('Customize')}
<DatasetCustomizationIndicator />
</TabLabel>
)}
Expand Down
36 changes: 18 additions & 18 deletions packages/nextclade-web/src/components/Results/RefNodeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,6 @@ interface Option {
description?: string
}

const builtinRefs: Option[] = [
{
value: REF_NODE_ROOT,
label: 'Reference',
description: 'Reference sequence',
},
{
value: REF_NODE_PARENT,
label: 'Parent',
description: 'Nearest node on reference tree',
},
{
value: REF_NODE_CLADE_FOUNDER,
label: 'Clade founder',
description: 'Earliest ancestor node with the same clade on reference tree',
},
]

export function RefNodeSelector() {
const { t } = useTranslationSafe()

Expand Down Expand Up @@ -64,6 +46,24 @@ export function RefNodeSelector() {
}
})

const builtinRefs: Option[] = [
{
value: REF_NODE_ROOT,
label: t('Reference'),
description: t('Reference sequence'),
},
{
value: REF_NODE_PARENT,
label: t('Parent'),
description: t('Nearest node on reference tree'),
},
{
value: REF_NODE_CLADE_FOUNDER,
label: t('Clade founder'),
description: t('Earliest ancestor node with the same clade on reference tree'),
},
]

const options = [...builtinRefs, ...cladeNodeAttrFounders, ...refs]
const currentOption = options.find((o) => o.value === currentRefNodeName)

Expand Down
28 changes: 15 additions & 13 deletions packages/nextclade-web/src/components/Results/ResultsStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { ReactNode, useCallback, useMemo, useState } from 'react'
import { useRecoilValue } from 'recoil'
import { FaCheckSquare as CheckIcon } from 'react-icons/fa'
import { IoWarning as WarnIcon } from 'react-icons/io5'
import i18n from 'src/i18n/i18n'
import { TFunc, useTranslationSafe } from 'src/helpers/useTranslationSafe'
import styled, { useTheme } from 'styled-components'
import { LoadingSpinner } from 'src/components/Loading/Loading'
import { Tooltip } from 'src/components/Results/Tooltip'
Expand Down Expand Up @@ -48,6 +48,7 @@ const ResultsStatusSpinnerWrapper = styled.span`
`

export function ResultsStatus({ ...restProps }) {
const { t } = useTranslationSafe()
const theme = useTheme()

const numThreads = useRecoilValue(numThreadsAtom)
Expand All @@ -59,7 +60,7 @@ export function ResultsStatus({ ...restProps }) {
const onMouseLeave = useCallback(() => setShowTooltip(false), [])

const { text, spinner } = useMemo(() => {
const { statusText, failureText, hasFailures } = selectStatus(statusGlobal, analysisResultStatuses, numThreads)
const { statusText, failureText, hasFailures } = selectStatus(statusGlobal, analysisResultStatuses, numThreads, t)

let text = <span>{statusText}</span>
if (failureText) {
Expand All @@ -81,7 +82,7 @@ export function ResultsStatus({ ...restProps }) {
)
}
return { text, spinner }
}, [analysisResultStatuses, numThreads, statusGlobal, theme.success, theme.warning])
}, [analysisResultStatuses, numThreads, statusGlobal, t, theme.success, theme.warning])

if (statusGlobal === AlgorithmGlobalStatus.idle) {
return null
Expand All @@ -104,6 +105,7 @@ export function selectStatus(
statusGlobal: AlgorithmGlobalStatus,
analysisResultStatuses: AlgorithmSequenceStatus[],
numThreads: number,
t: TFunc,
) {
const hasFailures = analysisResultStatuses.includes(AlgorithmSequenceStatus.failed)

Expand All @@ -114,29 +116,29 @@ export function selectStatus(
const treeBuildDonePercent = 90
const allDonePercent = 100

let statusText = i18n.t('Idle')
let statusText = t('Idle')
let failureText: string | undefined
let percent = 0

/* eslint-disable no-lone-blocks */
switch (statusGlobal) {
case AlgorithmGlobalStatus.idle:
{
statusText = i18n.t('Idle')
statusText = t('Idle')
percent = idlingPercent
}
break

case AlgorithmGlobalStatus.loadingData:
{
statusText = i18n.t('Loading data...')
statusText = t('Loading data...')
percent = loadingDataPercent
}
break

case AlgorithmGlobalStatus.initWorkers:
{
statusText = i18n.t('Starting {{numWorkers}} threads...', { numWorkers: numThreads })
statusText = t('Starting {{numWorkers}} threads...', { numWorkers: numThreads })
percent = loadingDataDonePercent
}
break
Expand All @@ -148,17 +150,17 @@ export function selectStatus(
const failed = analysisResultStatuses.filter((status) => status === AlgorithmSequenceStatus.failed).length
const done = succeeded + failed
percent = loadingDataDonePercent + (done / total) * (treeBuildPercent - loadingDataDonePercent)
statusText = i18n.t('Analysing sequences: Found: {{total}}. Analyzed: {{done}}', { done, total })
statusText = t('Analysing sequences: Found: {{total}}. Analyzed: {{done}}', { done, total })
if (failed > 0) {
failureText = i18n.t('Failed: {{failed}}', { failed, total })
failureText = t('Failed: {{failed}}', { failed, total })
}
}
break

case AlgorithmGlobalStatus.buildingTree:
{
percent = treeBuildDonePercent
statusText = i18n.t('Building tree')
statusText = t('Building tree')
}
break

Expand All @@ -168,16 +170,16 @@ export function selectStatus(
const total = analysisResultStatuses.length
const succeeded = analysisResultStatuses.filter((status) => status === AlgorithmSequenceStatus.done).length
const failed = analysisResultStatuses.filter((status) => status === AlgorithmSequenceStatus.failed).length
statusText = i18n.t('Done. Total sequences: {{total}}. Succeeded: {{succeeded}}', { succeeded, total })
statusText = t('Done. Total sequences: {{total}}. Succeeded: {{succeeded}}', { succeeded, total })
if (failed > 0) {
failureText = i18n.t('Failed: {{failed}}', { failed, total })
failureText = t('Failed: {{failed}}', { failed, total })
}
}
break

case AlgorithmGlobalStatus.failed:
{
failureText = i18n.t('Failed due to error.')
failureText = t('Failed due to error.')
percent = 100
}
break
Expand Down
8 changes: 5 additions & 3 deletions packages/nextclade-web/src/helpers/useTranslationSafe.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { StringMap, TOptions } from 'i18next'
import type { TOptions } from 'i18next'
import { useTranslation } from 'react-i18next'

export function useTranslationSafe<TInterpolationMap extends object = StringMap>() {
export type TFunc = (key: string, options?: string | TOptions | undefined) => string

export function useTranslationSafe(): { t: TFunc } {
const response = useTranslation()

function t(key: string, options?: TOptions<TInterpolationMap> | string) {
function t(key: string, options?: TOptions | string) {
return response.t(key, options) ?? key
}

Expand Down
Loading

0 comments on commit dcbc3b4

Please sign in to comment.