mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-06-07 08:56:58 +00:00
Add i18n and some languages
This commit is contained in:
parent
655c990917
commit
d2a82519ad
@ -21,4 +21,5 @@ Many additional details and a fully featured wiki will be created over time. Rea
|
|||||||
## Internationalization (i18n)
|
## Internationalization (i18n)
|
||||||
|
|
||||||
Qortal-Hub supports internationalization (i18n) using [i18next](https://www.i18next.com/), allowing seamless translation of UI text into multiple languages.
|
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/<locale>` folder.
|
||||||
|
48
i18n.js
Normal file
48
i18n.js
Normal file
@ -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;
|
10
package-lock.json
generated
10
package-lock.json
generated
@ -59,6 +59,7 @@
|
|||||||
"i18next": "^25.0.1",
|
"i18next": "^25.0.1",
|
||||||
"i18next-browser-languagedetector": "^8.0.5",
|
"i18next-browser-languagedetector": "^8.0.5",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"i18next-localstorage-backend": "^4.2.0",
|
||||||
"jssha": "3.3.1",
|
"jssha": "3.3.1",
|
||||||
"lit": "^3.2.1",
|
"lit": "^3.2.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@ -10846,6 +10847,15 @@
|
|||||||
"cross-fetch": "4.0.0"
|
"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": {
|
"node_modules/iconv-corefoundation": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
"i18next": "^25.0.1",
|
"i18next": "^25.0.1",
|
||||||
"i18next-browser-languagedetector": "^8.0.5",
|
"i18next-browser-languagedetector": "^8.0.5",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"i18next-localstorage-backend": "^4.2.0",
|
||||||
"jssha": "3.3.1",
|
"jssha": "3.3.1",
|
||||||
"lit": "^3.2.1",
|
"lit": "^3.2.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
10
public/locales/en/auth.json
Normal file
10
public/locales/en/auth.json
Normal file
@ -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"
|
||||||
|
}
|
9
public/locales/en/core.json
Normal file
9
public/locales/en/core.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"add": {
|
||||||
|
"task": "Add task"
|
||||||
|
},
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"description": "Description" ,
|
||||||
|
"save": "Save",
|
||||||
|
"title": "Title"
|
||||||
|
}
|
10
public/locales/it/auth.json
Normal file
10
public/locales/it/auth.json
Normal file
@ -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"
|
||||||
|
}
|
9
public/locales/it/core.json
Normal file
9
public/locales/it/core.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"add": {
|
||||||
|
"task": "Aggiungi compito"
|
||||||
|
},
|
||||||
|
"cancel": "Cancella",
|
||||||
|
"description": "Descrizione" ,
|
||||||
|
"save": "Salva",
|
||||||
|
"title": "Titolo"
|
||||||
|
}
|
@ -30,6 +30,7 @@ import { cleanUrl, gateways } from '../background';
|
|||||||
import { GlobalContext } from '../App';
|
import { GlobalContext } from '../App';
|
||||||
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
||||||
import ThemeSelector from '../components/Theme/ThemeSelector';
|
import ThemeSelector from '../components/Theme/ThemeSelector';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const manifestData = {
|
const manifestData = {
|
||||||
version: '0.5.3',
|
version: '0.5.3',
|
||||||
@ -84,6 +85,7 @@ export const NotAuthenticated = ({
|
|||||||
React.useState(null);
|
React.useState(null);
|
||||||
const { showTutorial, hasSeenGettingStarted } = useContext(GlobalContext);
|
const { showTutorial, hasSeenGettingStarted } = useContext(GlobalContext);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation('auth');
|
||||||
|
|
||||||
const importedApiKeyRef = useRef(null);
|
const importedApiKeyRef = useRef(null);
|
||||||
const currentNodeRef = useRef(null);
|
const currentNodeRef = useRef(null);
|
||||||
@ -183,6 +185,7 @@ export const NotAuthenticated = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
importedApiKeyRef.current = importedApiKey;
|
importedApiKeyRef.current = importedApiKey;
|
||||||
}, [importedApiKey]);
|
}, [importedApiKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
currentNodeRef.current = currentNode;
|
currentNodeRef.current = currentNode;
|
||||||
}, [currentNode]);
|
}, [currentNode]);
|
||||||
@ -309,6 +312,7 @@ export const NotAuthenticated = ({
|
|||||||
} else if (currentNodeRef.current) {
|
} else if (currentNodeRef.current) {
|
||||||
payload = currentNodeRef.current;
|
payload = currentNodeRef.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
|
|
||||||
const url = `${payload?.url}/admin/settings/localAuthBypassEnabled`;
|
const url = `${payload?.url}/admin/settings/localAuthBypassEnabled`;
|
||||||
@ -402,6 +406,7 @@ export const NotAuthenticated = ({
|
|||||||
const addCustomNode = () => {
|
const addCustomNode = () => {
|
||||||
setMode('add-node');
|
setMode('add-node');
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveCustomNodes = (myNodes, isFullListOfNodes) => {
|
const saveCustomNodes = (myNodes, isFullListOfNodes) => {
|
||||||
let nodes = [...(myNodes || [])];
|
let nodes = [...(myNodes || [])];
|
||||||
if (!isFullListOfNodes && customNodeToSaveIndex !== null) {
|
if (!isFullListOfNodes && customNodeToSaveIndex !== null) {
|
||||||
@ -455,7 +460,9 @@ export const NotAuthenticated = ({
|
|||||||
>
|
>
|
||||||
<img src={Logo1Dark} className="base-image" />
|
<img src={Logo1Dark} className="base-image" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
|
|
||||||
<TextP
|
<TextP
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@ -463,7 +470,7 @@ export const NotAuthenticated = ({
|
|||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
WELCOME TO
|
{t('auth:welcome', { postProcess: 'capitalize' })}
|
||||||
<TextSpan
|
<TextSpan
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
@ -504,13 +511,9 @@ export const NotAuthenticated = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CustomButton onClick={() => setExtstate('wallets')}>
|
<CustomButton onClick={() => setExtstate('wallets')}>
|
||||||
{/* <input {...getInputProps()} /> */}
|
{t('auth:account_many', { postProcess: 'capitalize' })}
|
||||||
Accounts
|
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
</HtmlTooltip>
|
</HtmlTooltip>
|
||||||
{/* <Tooltip title="Authenticate by importing your Qortal JSON file" arrow>
|
|
||||||
<img src={Info} />
|
|
||||||
</Tooltip> */}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Spacer height="6px" />
|
<Spacer height="6px" />
|
||||||
@ -565,10 +568,11 @@ export const NotAuthenticated = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Create account
|
{t('auth:create_account', { postProcess: 'capitalize' })}
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
</HtmlTooltip>
|
</HtmlTooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Spacer height="15px" />
|
<Spacer height="15px" />
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
@ -579,6 +583,7 @@ export const NotAuthenticated = ({
|
|||||||
>
|
>
|
||||||
{'Using node: '} {currentNode?.url}
|
{'Using node: '} {currentNode?.url}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
<Spacer height="15px" />
|
<Spacer height="15px" />
|
||||||
<Box
|
<Box
|
||||||
@ -603,7 +608,7 @@ export const NotAuthenticated = ({
|
|||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
For advanced users
|
{t('auth:advanced_users', { postProcess: 'capitalize' })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -6,6 +6,7 @@ import { MessageQueueProvider } from './MessageQueueContext.tsx';
|
|||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
|
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
|
||||||
import { CssBaseline } from '@mui/material';
|
import { CssBaseline } from '@mui/material';
|
||||||
|
import '../i18n';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<>
|
<>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user