Add translation system
This commit is contained in:
1 parent
a2775f8607
commit
4c30464ac9
7 files changed
+150
-7
No files matched your search
@@ -25,10 +25,12 @@
|
||||
"craco": "^0.0.3",
|
||||
"date-fns": "^2.22.1",
|
||||
"howler": "^2.2.0",
|
||||
"i18next": "^21.6.11",
|
||||
"node-sass": "^4.14.1",
|
||||
"react": "^16.13.1",
|
||||
"react-autocomplete": "^1.8.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-i18next": "^11.15.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "5.0.0",
|
||||
"typescript": "^3.7.5",
|
||||
|
||||
@@ -17,6 +17,25 @@ import { W2lTemplateUploadJob, W2lTemplateUploadJobStateResponsePayload } from '
|
||||
import { jobStateCanChange } from '@storyteller/components/src/jobs/JobStates';
|
||||
import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
|
||||
import { TtsCategoryType } from './AppWrapper';
|
||||
import { TRANSLATIONS } from './_i18n/Translations';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
i18n
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
// the translations
|
||||
// (tip move them in a JSON file and import them,
|
||||
// or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
|
||||
resources: TRANSLATIONS,
|
||||
//lng: 'en', // if you're using a language detector, do not define the lng option
|
||||
fallbackLng: 'en',
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
enum MigrationMode {
|
||||
NEW_VOCODES,
|
||||
@@ -165,25 +184,34 @@ class App extends React.Component<Props, State> {
|
||||
const showNotice = hasSpanish || hasPortuguese || hasTurkish; //|| hasIndonesian || hasGerman || hasJapanese;
|
||||
|
||||
let displayLanguage = Language.English;
|
||||
let languageCode = 'en';
|
||||
|
||||
if (hasSpanish) {
|
||||
displayLanguage = Language.Spanish;
|
||||
languageCode = 'es';
|
||||
} else if (hasPortuguese) {
|
||||
displayLanguage = Language.Portuguese;
|
||||
languageCode = 'pt';
|
||||
} else if (hasTurkish) {
|
||||
displayLanguage = Language.Turkish;
|
||||
languageCode = 'tr';
|
||||
} else if (hasIndonesian) {
|
||||
displayLanguage = Language.Indonesian;
|
||||
languageCode = 'id';
|
||||
} else if (hasGerman) {
|
||||
displayLanguage = Language.German;
|
||||
languageCode = 'de';
|
||||
} else if (hasJapanese) {
|
||||
displayLanguage = Language.Japanese;
|
||||
languageCode = 'ja';
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isShowingLanguageNotice: showNotice,
|
||||
displayLanguage: displayLanguage,
|
||||
});
|
||||
|
||||
i18n.changeLanguage(languageCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
const TRANSLATIONS : any = {
|
||||
en: {
|
||||
translation: {
|
||||
coreUi: {
|
||||
logout: 'Logout',
|
||||
},
|
||||
ttsListPage: {
|
||||
categoryFilters: 'Category Filters', // "Category / Language" in other locales
|
||||
loading: 'Loading...',
|
||||
search: 'Search',
|
||||
searchTerm: 'Search Term',
|
||||
speakButton: 'Speak',
|
||||
clearButton: 'Clear',
|
||||
}
|
||||
}
|
||||
},
|
||||
es: {
|
||||
translation: {
|
||||
coreUi: {
|
||||
logout: 'Cerrar sesión',
|
||||
},
|
||||
ttsListPage: {
|
||||
categoryFilters: 'Categoría / Idioma',
|
||||
loading: 'Cargando...',
|
||||
search: 'Búsqueda',
|
||||
searchTerm: 'Término de búsqueda',
|
||||
speakButton: 'Hablar',
|
||||
clearButton: 'Claro',
|
||||
}
|
||||
}
|
||||
},
|
||||
pt: {
|
||||
translation: {
|
||||
coreUi: {
|
||||
logout: 'Sair',
|
||||
},
|
||||
ttsListPage: {
|
||||
categoryFilters: 'Categoria / Idioma',
|
||||
loading: 'Carregando...',
|
||||
search: 'Procurar',
|
||||
searchTerm: 'Termo de pesquisa',
|
||||
speakButton: 'Falar',
|
||||
clearButton: 'Claro',
|
||||
}
|
||||
}
|
||||
},
|
||||
ja: {
|
||||
translation: {
|
||||
coreUi: {
|
||||
logout: 'ログアウト',
|
||||
},
|
||||
ttsListPage: {
|
||||
categoryFilters: 'カテゴリ / 言語',
|
||||
loading: '読み込んでいます...',
|
||||
search: '検索',
|
||||
searchTerm: '検索語',
|
||||
speakButton: '話す',
|
||||
clearButton: 'クリア',
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export { TRANSLATIONS }
|
||||
@@ -5,6 +5,7 @@ import { TtsCategoryType } from '../../../../AppWrapper';
|
||||
import Autocomplete from 'react-autocomplete';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||
import { t } from 'i18next';
|
||||
|
||||
// NB: This probably is not the best autocomplete library in the world
|
||||
// A lot of the libraries are really old and depend on jQuery (gross).
|
||||
@@ -36,7 +37,7 @@ export function AutocompleteSearch(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className="field">
|
||||
<strong style={{ display: "block", margin: "7px 0 0 0"}}>Search</strong>
|
||||
<strong style={{ display: "block", margin: "7px 0 0 0"}}>{t('ttsListPage.search')}</strong>
|
||||
<div className="control has-icons-left">
|
||||
|
||||
{/* NB: See note above about this library. */}
|
||||
@@ -44,7 +45,11 @@ export function AutocompleteSearch(props: Props) {
|
||||
getItemValue={(item : TtsModelListItem) => item.title}
|
||||
items={props.allTtsModels}
|
||||
renderInput={(props) => (
|
||||
<input className="input" type="text" placeholder="search" value={searchValue} {...props} />
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder={t('ttsListPage.searchTerm')}
|
||||
value={searchValue} {...props} />
|
||||
)}
|
||||
renderMenu={children => (
|
||||
<div className="menu">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronRight, faHeadphonesAlt, faTags, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
|
||||
import { TtsCategoryType } from '../../../../AppWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
allTtsCategories: TtsCategoryType[],
|
||||
@@ -36,6 +37,8 @@ export function MultiDropdownSearch(props: Props) {
|
||||
maybeSelectedTtsModel,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isLoading = allTtsModels.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -257,14 +260,14 @@ export function MultiDropdownSearch(props: Props) {
|
||||
if (isLoading) {
|
||||
selectClasses = 'select is-normal is-loading';
|
||||
loadingOption = (
|
||||
<option key="waiting" value="" disabled={true}>Loading...</option>
|
||||
<option key="waiting" value="" disabled={true}>{t('ttsListPage.loading')}</option>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Category Dropdowns */}
|
||||
<strong>Category Filters</strong>
|
||||
<strong>{t('ttsListPage.categoryFilters')}</strong>
|
||||
<br />
|
||||
{categoryFieldGroups}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { SyntheticCategory, TtsCategoryType } from '../../../../AppWrapper';
|
||||
import { AutocompleteSearch } from './AutocompleteSearch';
|
||||
import { LanguageNotice } from './LanguageNotice';
|
||||
import { Language } from '@storyteller/components/src/i18n/Language';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export interface EnqueueJobResponsePayload {
|
||||
success: boolean,
|
||||
@@ -323,10 +324,14 @@ function TtsModelListFc(props: Props) {
|
||||
<div className="button-group">
|
||||
<div className="columns is-mobile">
|
||||
<div className="column has-text-centered">
|
||||
<button className="button is-danger is-large" disabled={remainingCharactersButtonDisabled}>Speak</button>
|
||||
<button
|
||||
className="button is-danger is-large"
|
||||
disabled={remainingCharactersButtonDisabled}>{t('ttsListPage.speakButton')}</button>
|
||||
</div>
|
||||
<div className="column has-text-centered">
|
||||
<button className="button is-danger is-light is-large" onClick={handleClearClick}>Clear</button>
|
||||
<button
|
||||
className="button is-danger is-light is-large"
|
||||
onClick={handleClearClick}>{t('ttsListPage.clearButton')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1022,6 +1022,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.12.0", "@babel/runtime@^7.14.5":
|
||||
version "7.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
|
||||
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.16.7", "@babel/template@^7.3.3":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
|
||||
@@ -7378,7 +7385,7 @@ html-entities@^2.1.0, html-entities@^2.3.2:
|
||||
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488"
|
||||
integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==
|
||||
|
||||
html-escaper@^2.0.0:
|
||||
html-escaper@^2.0.0, html-escaper@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||
@@ -7396,6 +7403,13 @@ html-minifier-terser@^6.0.2:
|
||||
relateurl "^0.2.7"
|
||||
terser "^5.10.0"
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
html-webpack-plugin@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50"
|
||||
@@ -7516,6 +7530,13 @@ humanize-ms@^1.2.1:
|
||||
dependencies:
|
||||
ms "^2.0.0"
|
||||
|
||||
i18next@^21.6.11:
|
||||
version "21.6.11"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.11.tgz#de6d649221e02474cc701d152606bf3e61e37bcd"
|
||||
integrity sha512-tJ2+o0lVO+fhi8bPkCpBAeY1SgkqmQm5NzgPWCQssBrywJw98/o+Kombhty5nxQOpHtvMmsxcOopczUiH6bJxQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.0"
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@@ -11439,6 +11460,15 @@ react-error-overlay@^6.0.10:
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
|
||||
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
|
||||
|
||||
react-i18next@^11.15.4:
|
||||
version "11.15.4"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.15.4.tgz#fe0c792ea93f038548e838cecae3ed4173822937"
|
||||
integrity sha512-jKJNAcVcbPGK+yrTcXhLblgPY16n6NbpZZL3Mk8nswj1v3ayIiUBVDU09SgqnT+DluyQBS97hwSvPU5yVFG0yg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.5"
|
||||
html-escaper "^2.0.2"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.4:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
@@ -13724,6 +13754,11 @@ vm-browserify@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||
|
||||
void-elements@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
|
||||
|
||||
w3c-hr-time@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
|
||||
|
||||
Reference in new issue
Block a user