Add translation system

This commit is contained in:
Brandon Thomas committed 2022-02-12 22:44:20 -05:00
1 parent a2775f8607
commit 4c30464ac9
7 files changed
+150 -7

No files matched your search

+2
View File
@@ -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",
+28
View File
@@ -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>
+36 -1
View File
@@ -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"