From 4c30464ac9fe7828bb38ae5f3e8c2cd148fc016d Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sat, 12 Feb 2022 22:44:20 -0500 Subject: [PATCH] Add translation system --- packages/fakeyou/package.json | 2 + packages/fakeyou/src/App.tsx | 28 ++++++++ packages/fakeyou/src/_i18n/Translations.ts | 65 +++++++++++++++++++ .../tts/tts_model_list/AutocompleteSearch.tsx | 9 ++- .../tts_model_list/MultiDropdownSearch.tsx | 7 +- .../tts/tts_model_list/TtsModelListFc.tsx | 9 ++- yarn.lock | 37 ++++++++++- 7 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 packages/fakeyou/src/_i18n/Translations.ts diff --git a/packages/fakeyou/package.json b/packages/fakeyou/package.json index f3e484d15..e569d6e66 100644 --- a/packages/fakeyou/package.json +++ b/packages/fakeyou/package.json @@ -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", diff --git a/packages/fakeyou/src/App.tsx b/packages/fakeyou/src/App.tsx index 305551600..9fe412c3b 100644 --- a/packages/fakeyou/src/App.tsx +++ b/packages/fakeyou/src/App.tsx @@ -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 { 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); } } diff --git a/packages/fakeyou/src/_i18n/Translations.ts b/packages/fakeyou/src/_i18n/Translations.ts new file mode 100644 index 000000000..2f939a825 --- /dev/null +++ b/packages/fakeyou/src/_i18n/Translations.ts @@ -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 } diff --git a/packages/fakeyou/src/v2/view/tts/tts_model_list/AutocompleteSearch.tsx b/packages/fakeyou/src/v2/view/tts/tts_model_list/AutocompleteSearch.tsx index c2112883b..b4730664d 100644 --- a/packages/fakeyou/src/v2/view/tts/tts_model_list/AutocompleteSearch.tsx +++ b/packages/fakeyou/src/v2/view/tts/tts_model_list/AutocompleteSearch.tsx @@ -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 ( <>
- Search + {t('ttsListPage.search')}
{/* 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) => ( - + )} renderMenu={children => (
diff --git a/packages/fakeyou/src/v2/view/tts/tts_model_list/MultiDropdownSearch.tsx b/packages/fakeyou/src/v2/view/tts/tts_model_list/MultiDropdownSearch.tsx index 836b62661..7916ad53f 100644 --- a/packages/fakeyou/src/v2/view/tts/tts_model_list/MultiDropdownSearch.tsx +++ b/packages/fakeyou/src/v2/view/tts/tts_model_list/MultiDropdownSearch.tsx @@ -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 = ( - + ) } return (
{/* Category Dropdowns */} - Category Filters + {t('ttsListPage.categoryFilters')}
{categoryFieldGroups} diff --git a/packages/fakeyou/src/v2/view/tts/tts_model_list/TtsModelListFc.tsx b/packages/fakeyou/src/v2/view/tts/tts_model_list/TtsModelListFc.tsx index 909b069ed..b57ddd461 100644 --- a/packages/fakeyou/src/v2/view/tts/tts_model_list/TtsModelListFc.tsx +++ b/packages/fakeyou/src/v2/view/tts/tts_model_list/TtsModelListFc.tsx @@ -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) {
- +
- +
diff --git a/yarn.lock b/yarn.lock index 2e993eb34..218d88420 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"