started internationalization

This commit is contained in:
PhilReact 2025-05-20 17:56:54 +03:00
parent 18151bfa9b
commit aa7452713f
13 changed files with 443 additions and 84 deletions

BIN
dist.zip Normal file

Binary file not shown.

267
package-lock.json generated
View File

@ -12,10 +12,12 @@
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.0.1",
"@mui/material": "^7.0.1",
"i18next": "^25.1.2",
"jotai": "^2.12.3",
"qapp-core": "^1.0.30",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-i18next": "^15.5.1",
"react-router-dom": "^7.3.0",
"react-virtuoso": "^4.12.7"
},
@ -48,13 +50,14 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.9",
"@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
@ -169,17 +172,19 @@
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@ -194,24 +199,26 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
"integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/template": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.9"
"@babel/types": "^7.27.1"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -259,13 +266,14 @@
}
},
"node_modules/@babel/template": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/code-frame": "^7.27.1",
"@babel/parser": "^7.27.2",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@ -297,12 +305,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9"
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@ -1672,11 +1681,6 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -2270,6 +2274,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
@ -2849,6 +2854,46 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"license": "MIT",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/i18next": {
"version": "25.2.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.0.tgz",
"integrity": "sha512-ERhJICsxkw1vE7G0lhCUYv4ZxdBEs03qblt1myJs94rYRK9loJF3xDj8mgQz3LmCyp0yYrNjbN/1/GWZTZDGCA==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.1"
},
"peerDependencies": {
"typescript": "^5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -3554,6 +3599,32 @@
"react-dom": ">=16"
}
},
"node_modules/react-i18next": {
"version": "15.5.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz",
"integrity": "sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.0",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"react": ">= 16.8.0",
"typescript": "^5"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/react-intersection-observer": {
"version": "9.16.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz",
@ -3583,14 +3654,13 @@
}
},
"node_modules/react-router": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.3.0.tgz",
"integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
"integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
@ -3606,11 +3676,12 @@
}
},
"node_modules/react-router-dom": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.3.0.tgz",
"integrity": "sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
"integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
"license": "MIT",
"dependencies": {
"react-router": "7.3.0"
"react-router": "7.6.0"
},
"engines": {
"node": ">=20.0.0"
@ -3770,7 +3841,8 @@
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
@ -3859,6 +3931,51 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -3888,11 +4005,6 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/turbo-stream": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -3909,7 +4021,7 @@
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -3988,14 +4100,18 @@
}
},
"node_modules/vite": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz",
"integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==",
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
"picomatch": "^4.0.2",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
"rollup": "^4.34.9",
"tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"
@ -4058,6 +4174,43 @@
}
}
},
"node_modules/vite/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -20,7 +20,9 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.3.0",
"react-virtuoso": "^4.12.7"
"react-virtuoso": "^4.12.7",
"i18next": "^25.1.2",
"react-i18next": "^15.5.1"
},
"devDependencies": {
"@eslint/js": "^9.21.0",

View File

@ -30,6 +30,7 @@ import {
primaryNameAtom,
} from '../state/global/names';
import { Availability } from '../interfaces';
import { useTranslation } from 'react-i18next';
const Label = styled('label')`
display: block;
@ -40,6 +41,8 @@ const Label = styled('label')`
`;
const RegisterName = () => {
const { t } = useTranslation(['core']);
const [isOpen, setIsOpen] = useState(false);
const balance = useGlobal().auth.balance;
const setNames = useSetAtom(namesAtom);
@ -77,7 +80,11 @@ const RegisterName = () => {
}, [namesForSale, primaryName, pendingTxs]);
const registerNameFunc = async () => {
if (!address) return;
const loadId = showLoading('Registering name...please wait');
const loadId = showLoading(
t('core:new_name.responses.loading', {
postProcess: 'capitalize',
})
);
try {
setIsLoadingRegisterName(true);
const res = await qortalRequest({
@ -105,14 +112,22 @@ const RegisterName = () => {
},
};
});
showSuccess('Successfully registered a name');
showSuccess(
t('core:new_name.responses.success', {
postProcess: 'capitalize',
})
);
setNameValue('');
setIsOpen(false);
} catch (error) {
if (error instanceof Error) {
showError(error.message);
} else {
showError('Unable to register name');
showError(
t('core:new_name.responses.error', {
postProcess: 'capitalize',
})
);
}
} finally {
setIsLoadingRegisterName(false);
@ -175,14 +190,20 @@ const RegisterName = () => {
flexShrink: 0,
}}
>
new name
{t('core:actions.new_name', {
postProcess: 'capitalize',
})}
</Button>
<Dialog
open={isOpen}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{'Register name'}</DialogTitle>
<DialogTitle id="alert-dialog-title">
{t('core:actions.register_name', {
postProcess: 'capitalize',
})}
</DialogTitle>
<DialogContent>
<Box
sx={{
@ -197,13 +218,15 @@ const RegisterName = () => {
padding: '10px',
}}
>
<Label>Choose a name</Label>
<Label></Label>
<TextField
autoComplete="off"
autoFocus
onChange={(e) => setNameValue(e.target.value)}
value={nameValue}
placeholder="Choose a name"
placeholder={t('core:new_name.choose_name', {
postProcess: 'capitalize',
})}
/>
{(!balance || (nameFee && balance && balance < nameFee)) && (
<>
@ -221,8 +244,11 @@ const RegisterName = () => {
}}
/>
<Typography>
Your balance is {balance ?? 0} QORT. A name registration
requires a {nameFee} QORT fee
{t('balance_message', {
balance: balance ?? 0,
nameFee,
postProcess: 'capitalize',
})}
</Typography>
</Box>
<Spacer height="10px" />
@ -242,7 +268,9 @@ const RegisterName = () => {
color: theme.palette.text.primary,
}}
/>
<Typography>{nameValue} is available</Typography>
<Typography>
{t('core:new_name.name_available', { name: nameValue })}
</Typography>
</Box>
)}
{isNameAvailable === Availability.NOT_AVAILABLE && (
@ -258,7 +286,9 @@ const RegisterName = () => {
color: theme.palette.text.primary,
}}
/>
<Typography>{nameValue} is unavailable</Typography>
<Typography>
{t('core:new_name.name_unavailable', { name: nameValue })}
</Typography>
</Box>
)}
{isNameAvailable === Availability.LOADING && (
@ -270,7 +300,11 @@ const RegisterName = () => {
}}
>
<BarSpinner width="16px" color={theme.palette.text.primary} />
<Typography>Checking if name already existis</Typography>
<Typography>
{t('core:new_name.checking_name', {
postProcess: 'capitalize',
})}
</Typography>
</Box>
)}
</Box>
@ -284,7 +318,9 @@ const RegisterName = () => {
setNameValue('');
}}
>
Close
{t('core:actions.close', {
postProcess: 'capitalize',
})}
</Button>
<Button
disabled={Boolean(
@ -298,7 +334,9 @@ const RegisterName = () => {
onClick={registerNameFunc}
autoFocus
>
Register Name
{t('core:actions.register_name', {
postProcess: 'capitalize',
})}
</Button>
</DialogActions>
</Dialog>

View File

@ -2,14 +2,21 @@ import { useEffect } from 'react';
import { To, useNavigate } from 'react-router-dom';
import { EnumTheme, themeAtom } from '../state/global/system';
import { useSetAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
import { supportedLanguages } from '../i18n/i18n';
type Language = 'de' | 'en' | 'es' | 'fr' | 'it' | 'ru';
type Theme = 'dark' | 'light';
interface CustomWindow extends Window {
_qdnTheme: string;
_qdnTheme: Theme;
_qdnLang: Language;
}
const customWindow = window as unknown as CustomWindow;
export const useIframe = () => {
const setTheme = useSetAtom(themeAtom);
const { i18n } = useTranslation();
const navigate = useNavigate();
useEffect(() => {
@ -19,8 +26,20 @@ export const useIframe = () => {
} else if (themeColorDefault === 'light') {
setTheme(EnumTheme.LIGHT);
}
const languageDefault = customWindow?._qdnLang;
if (supportedLanguages?.includes(languageDefault)) {
i18n.changeLanguage(languageDefault);
}
function handleNavigation(event: {
data: { action: string; path: To; theme: 'dark' | 'light' };
data: {
action: string;
path: To;
theme: Theme;
language: Language;
};
}) {
if (event.data?.action === 'NAVIGATE_TO_PATH' && event.data.path) {
navigate(event.data.path); // Navigate directly to the specified path
@ -37,6 +56,12 @@ export const useIframe = () => {
} else if (themeColor === 'light') {
setTheme(EnumTheme.LIGHT);
}
} else if (
event.data?.action === 'LANGUAGE_CHANGED' &&
event.data.language
) {
if (!supportedLanguages?.includes(event.data.language)) return;
i18n.changeLanguage(event.data.language);
}
}

56
src/i18n/i18n.ts Normal file
View File

@ -0,0 +1,56 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import {
capitalizeAll,
capitalizeFirstChar,
capitalizeFirstWord,
} from './processors';
// Load all locale JSON files
const modules = import.meta.glob('./locales/**/*.json', {
eager: true,
}) as Record<string, any>;
// Dynamically detect unique language codes
export const supportedLanguages: string[] = Array.from(
new Set(
Object.keys(modules)
.map((path) => {
const match = path.match(/\.\/locales\/([^/]+)\//);
return match ? match[1] : null;
})
.filter((lang): lang is string => typeof lang === 'string')
)
);
// Construct i18n resources object
const resources: Record<string, Record<string, any>> = {};
for (const path in modules) {
// Path format: './locales/en/core.json'
const match = path.match(/\.\/locales\/([^/]+)\/([^/]+)\.json$/);
if (!match) continue;
const [, lang, ns] = match;
resources[lang] = resources[lang] || {};
resources[lang][ns] = modules[path].default;
}
i18n
.use(initReactI18next)
.use(capitalizeAll as any)
.use(capitalizeFirstChar as any)
.use(capitalizeFirstWord as any)
.init({
resources,
fallbackLng: 'en',
lng: navigator.language,
supportedLngs: supportedLanguages,
ns: ['core'],
defaultNS: 'core',
interpolation: { escapeValue: false },
react: { useSuspense: false },
debug: import.meta.env.MODE === 'development',
});
export default i18n;

View File

@ -0,0 +1,26 @@
{
"header": {
"my_names": "my names",
"market": "names for sale"
},
"inputs": {
"filter_names": "filter names"
},
"actions": {
"new_name": "new name",
"register_name": "register name",
"close": "close"
},
"new_name": {
"choose_name": "choose a name",
"balance_message": "Your balance is {{balance}} QORT. A name registration requires a {{nameFee}} QORT fee.",
"name_available": "{{name}} is available",
"name_unavailable": "{{name}} is unavailable",
"checking_name": "checking if name already exists",
"responses": {
"success": "successfully registered a name",
"error": "unable to register name",
"loading": "Registering name...please wait"
}
}
}

View File

@ -0,0 +1,6 @@
{
"header": {
"my_names": "mis nombres",
"market": ""
}
}

32
src/i18n/processors.ts Normal file
View File

@ -0,0 +1,32 @@
export const capitalizeAll = {
type: 'postProcessor',
name: 'capitalizeAll',
process: (value: string) => value.toUpperCase(),
};
export const capitalizeFirstChar = {
type: 'postProcessor',
name: 'capitalizeFirstChar',
process: (value: string) => value.charAt(0).toUpperCase() + value.slice(1),
};
export const capitalizeFirstWord = {
type: 'postProcessor',
name: 'capitalizeFirstWord',
process: (value: string) => {
if (!value?.trim()) return value;
const trimmed = value.trimStart();
const firstSpaceIndex = trimmed.indexOf(' ');
if (firstSpaceIndex === -1) {
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
}
const firstWord = trimmed.slice(0, firstSpaceIndex);
const restOfString = trimmed.slice(firstSpaceIndex);
const trailingSpaces = value.slice(trimmed.length);
return firstWord.toUpperCase() + restOfString + trailingSpaces;
},
};

View File

@ -1,10 +1,11 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import ThemeProviderWrapper from "./styles/theme/theme-provider.tsx";
import { AppWrapper } from "./AppWrapper.tsx";
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import ThemeProviderWrapper from './styles/theme/theme-provider.tsx';
import { AppWrapper } from './AppWrapper.tsx';
import './i18n/i18n.ts';
createRoot(document.getElementById("root")!).render(
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ThemeProviderWrapper>
<AppWrapper />

View File

@ -8,8 +8,11 @@ import {
} from '../state/global/names';
import { useAtom } from 'jotai';
import { SortBy, SortDirection } from '../interfaces';
import { useTranslation } from 'react-i18next';
export const Market = () => {
const { t } = useTranslation(['core']);
const [namesForSale] = useAtom(forSaleAtom);
const [pendingTxs] = useAtom(pendingTxsAtom);
const [primaryName] = useAtom(primaryNameAtom);
@ -106,7 +109,9 @@ export const Market = () => {
}}
>
<TextField
placeholder="Filter names"
placeholder={t('core:inputs.filter_names', {
postProcess: 'capitalize',
})}
value={value}
onChange={(e) => setValue(e.target.value)}
size="small"

View File

@ -4,8 +4,11 @@ import { namesAtom, primaryNameAtom } from '../state/global/names';
import { NameTable } from '../components/Tables/NameTable';
import { Box, TextField } from '@mui/material';
import RegisterName from '../components/RegisterName';
import { useTranslation } from 'react-i18next';
export const MyNames = () => {
const { t } = useTranslation(['core']);
const [names] = useAtom(namesAtom);
const [value, setValue] = useState('');
const [filterValue, setFilterValue] = useState('');
@ -50,7 +53,9 @@ export const MyNames = () => {
>
{' '}
<TextField
placeholder="Filter names"
placeholder={t('core:inputs.filter_names', {
postProcess: 'capitalize',
})}
value={value}
onChange={(e) => setValue(e.target.value)}
size="small"

View File

@ -4,16 +4,26 @@ import { AppBar, Toolbar, Button, Box, useTheme } from '@mui/material';
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
import StorefrontIcon from '@mui/icons-material/Storefront';
import { PendingTxsTable } from '../components/Tables/PendingTxsTable';
import { useTranslation } from 'react-i18next';
const Layout = () => {
useIframe();
const navigate = useNavigate();
const location = useLocation();
const theme = useTheme();
const { t } = useTranslation(['core']);
const navItems = [
{ label: 'My names', path: '/', Icon: FormatListBulletedIcon },
{ label: 'Names for sale', path: '/market', Icon: StorefrontIcon },
{
label: t('core:header.my_names', { postProcess: 'capitalize' }),
path: '/',
Icon: FormatListBulletedIcon,
},
{
label: t('core:header.market', { postProcess: 'capitalize' }),
path: '/market',
Icon: StorefrontIcon,
},
];
return (