diff --git a/README.md b/README.md index 8e12f3e..4a62e6d 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,5 @@ Many additional details and a fully featured wiki will be created over time. Rea ## Internationalization (i18n) Qortal-Hub supports internationalization (i18n) using [i18next](https://www.i18next.com/), allowing seamless translation of UI text into multiple languages. -The setup includes modularized translation files, language detection, and runtime language switching. +The setup includes modularized translation files, language detection, context and runtime language switching. +Files with translation are in `public/locales/` folder. diff --git a/i18n.js b/i18n.js new file mode 100644 index 0000000..09886c5 --- /dev/null +++ b/i18n.js @@ -0,0 +1,48 @@ +import { initReactI18next } from 'react-i18next'; +import HttpBackend from 'i18next-http-backend'; +import LocalStorageBackend from 'i18next-localstorage-backend'; +import HttpApi from 'i18next-http-backend'; +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +// Detect environment +const isDev = process.env.NODE_ENV === 'development'; + +// Register custom postProcessor: it capitalizes the first letter of a translation- +// Usage: +// t('greeting', { postProcess: 'capitalize' }) +const capitalize = { + type: 'postProcessor', + name: 'capitalize', + process: (value, key, options, translator) => { + return value.charAt(0).toUpperCase() + value.slice(1); + }, +}; + +i18n + .use(HttpApi) + .use(LanguageDetector) + .use(initReactI18next) + .use(capitalize) + .init({ + debug: isDev, + fallbackLng: 'en', + ns: ['auth', 'core'], + supportedLngs: ['en', 'it', 'fr', 'es'], + backend: { + backends: [LocalStorageBackend, HttpBackend], + backendOptions: [ + { + expirationTime: 7 * 24 * 60 * 60 * 1000, // 7 days + }, + { + loadPath: '/locales/{{lng}}/{{ns}}.json', + }, + ], + }, + interpolation: { + escapeValue: false, + }, + }); + +export default i18n; diff --git a/package-lock.json b/package-lock.json index b98590c..071586f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", "i18next-http-backend": "^3.0.2", + "i18next-localstorage-backend": "^4.2.0", "jssha": "3.3.1", "lit": "^3.2.1", "lodash": "^4.17.21", @@ -10846,6 +10847,15 @@ "cross-fetch": "4.0.0" } }, + "node_modules/i18next-localstorage-backend": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/i18next-localstorage-backend/-/i18next-localstorage-backend-4.2.0.tgz", + "integrity": "sha512-vglEQF0AnLriX7dLA2drHnqAYzHxnLwWQzBDw8YxcIDjOvYZz5rvpal59Dq4In+IHNmGNM32YgF0TDjBT0fHmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.15" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", diff --git a/package.json b/package.json index 3e37c8f..0060144 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", "i18next-http-backend": "^3.0.2", + "i18next-localstorage-backend": "^4.2.0", "jssha": "3.3.1", "lit": "^3.2.1", "lodash": "^4.17.21", diff --git a/public/locales/en/auth.json b/public/locales/en/auth.json new file mode 100644 index 0000000..2c0bd67 --- /dev/null +++ b/public/locales/en/auth.json @@ -0,0 +1,10 @@ +{ + "account_many": "accounts", + "account_one": "account", + "advanced_users": "for advanced users", + "create_account": "create account", + "welcome": "Welcome to", + "use_local_node": "use local node", + "change_apikey": "change API key", + "choose_custom_node": "choose custom node" +} diff --git a/public/locales/en/core.json b/public/locales/en/core.json new file mode 100644 index 0000000..d8f5ca1 --- /dev/null +++ b/public/locales/en/core.json @@ -0,0 +1,9 @@ +{ + "add": { + "task": "Add task" + }, + "cancel": "Cancel", + "description": "Description" , + "save": "Save", + "title": "Title" +} diff --git a/public/locales/it/auth.json b/public/locales/it/auth.json new file mode 100644 index 0000000..c72fbc7 --- /dev/null +++ b/public/locales/it/auth.json @@ -0,0 +1,10 @@ +{ + "account_many": "account", + "account_one": "account", + "advanced_users": "Per utenti avanzati", + "change_apikey": "Cambia la chiave API", + "choose_custom_node": "Scegli un nodo custom", + "create_account": "crea un account", + "use_local_node": "Usa nodo locale", + "welcome": "Benvenuto in" +} diff --git a/public/locales/it/core.json b/public/locales/it/core.json new file mode 100644 index 0000000..d23032e --- /dev/null +++ b/public/locales/it/core.json @@ -0,0 +1,9 @@ +{ + "add": { + "task": "Aggiungi compito" + }, + "cancel": "Cancella", + "description": "Descrizione" , + "save": "Salva", + "title": "Titolo" +} diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx index a892414..f1b8ece 100644 --- a/src/ExtStates/NotAuthenticated.tsx +++ b/src/ExtStates/NotAuthenticated.tsx @@ -30,6 +30,7 @@ import { cleanUrl, gateways } from '../background'; import { GlobalContext } from '../App'; import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import ThemeSelector from '../components/Theme/ThemeSelector'; +import { useTranslation } from 'react-i18next'; const manifestData = { version: '0.5.3', @@ -84,6 +85,7 @@ export const NotAuthenticated = ({ React.useState(null); const { showTutorial, hasSeenGettingStarted } = useContext(GlobalContext); const theme = useTheme(); + const { t } = useTranslation('auth'); const importedApiKeyRef = useRef(null); const currentNodeRef = useRef(null); @@ -183,6 +185,7 @@ export const NotAuthenticated = ({ useEffect(() => { importedApiKeyRef.current = importedApiKey; }, [importedApiKey]); + useEffect(() => { currentNodeRef.current = currentNode; }, [currentNode]); @@ -309,6 +312,7 @@ export const NotAuthenticated = ({ } else if (currentNodeRef.current) { payload = currentNodeRef.current; } + let isValid = false; const url = `${payload?.url}/admin/settings/localAuthBypassEnabled`; @@ -402,6 +406,7 @@ export const NotAuthenticated = ({ const addCustomNode = () => { setMode('add-node'); }; + const saveCustomNodes = (myNodes, isFullListOfNodes) => { let nodes = [...(myNodes || [])]; if (!isFullListOfNodes && customNodeToSaveIndex !== null) { @@ -455,7 +460,9 @@ export const NotAuthenticated = ({ > + + - WELCOME TO + {t('auth:welcome', { postProcess: 'capitalize' })} setExtstate('wallets')}> - {/* */} - Accounts + {t('auth:account_many', { postProcess: 'capitalize' })} - {/* - - */} @@ -565,10 +568,11 @@ export const NotAuthenticated = ({ }, }} > - Create account + {t('auth:create_account', { postProcess: 'capitalize' })} + {'Using node: '} {currentNode?.url} + <> - For advanced users + {t('auth:advanced_users', { postProcess: 'capitalize' })}