implement i18next and i18next-scanner

This commit is contained in:
Jon Eugster
2024-03-24 17:37:31 +01:00
parent c24efb1377
commit 45bc0468df
9 changed files with 780 additions and 42 deletions
+101
View File
@@ -0,0 +1,101 @@
const typescriptTransform = require('i18next-scanner-typescript');
const fs = require('fs');
const chalk = require('chalk');
module.exports = {
input: [
'client/src/**/*.{tsx,ts}',
// Use ! to filter out files or directories
'!client/i18n/**',
'!**/node_modules/**',
],
output: './client/public/locales',
options: {
debug: true,
func: {
list: ['i18next.t', 'i18n.t', 't'],
extensions: ['.js', '.jsx'] // not .ts or .tsx since we use i18next-scanner-typescript!
},
trans: {
component: 'Trans',
i18nKey: 'i18nKey',
defaultsKey: 'defaults',
extensions: ['.js', '.jsx'], // not .ts or .tsx since we use i18next-scanner-typescript!
fallbackKey: (ns, value) => {return value},
// https://react.i18next.com/latest/trans-component#usage-with-simple-html-elements-like-less-than-br-greater-than-and-others-v10.4.0
supportBasicHtmlNodes: true, // Enables keeping the name of simple nodes (e.g. <br/>) in translations instead of indexed keys.
keepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // Which nodes are allowed to be kept in translations during defaultValue generation of <Trans>.
// // https://github.com/acornjs/acorn/tree/master/acorn#interface
// acorn: {
// ecmaVersion: 2020,
// sourceType: 'module', // defaults to 'module'
// }
},
lngs: ['en','de'],
ns: [],
defaultLng: 'en',
defaultNs: 'translation',
defaultValue: (lng, ns, key) => {
if (lng === 'en') {
return key; // Use key as value for base language
}
return ''; // Return empty string for other languages
},
resource: {
loadPath: './{{lng}}/{{ns}}.json',
savePath: './{{lng}}/{{ns}}.json',
jsonIndent: 2,
lineEnding: '\n'
},
nsSeparator: false, // namespace separator
keySeparator: false, // key separator
plurals: false,
interpolation: {
prefix: '{{',
suffix: '}}'
},
metadata: {},
allowDynamicKeys: false,
},
transform: typescriptTransform(
// options
{
// default value for extensions
extensions: [".ts", ".tsx"],
// optional ts configuration
tsOptions: {
target: "es2017",
},
},
function(outputText, file, enc, done) {
'use strict';
const parser = this.parser;
parser.parseTransFromString(outputText);
parser.parseFuncFromString(outputText);
// const content = fs.readFileSync(file.path, enc);
// let count = 0;
// parser.parseFuncFromString(content, { list: ['i18n._', 'i18n.__'] }, (key, options) => {
// parser.set(key, Object.assign({}, options, {
// nsSeparator: false,
// keySeparator: false
// }));
// ++count;
// });
// if (count > 0) {
// console.log(`[i18next-scanner] transform: count=${chalk.cyan(count)}, file=${chalk.yellow(JSON.stringify(file.relative))}`);
// }
done();
}
),
};
+11
View File
@@ -0,0 +1,11 @@
{
"Lean Game Server": "Lean-Spieleserver",
"<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>": "<p>Die Spielregeln bestimmen ob es erlaubt ist, Levels zu überspringen und ob das Spiel überprüft welche Taktiken und Theoreme freigeschaltet sind und nur diese im Beweis akzeptiert.</p><1>Bemerkung: \"Freigeschaltete\" Taktiken (und Theoreme) werden durch zwei Faktoren bestimmt: The Menge der Taktiken die minimal notwending sind um den Level zu lösen und dazu die Menge aller Taktiken, die in einem anderen Level freigeschaltet wurden. Das bedeutet wenn <1>simp</1> in einem Level freigeschaltet wird, kann diese Taktik danach in jeglichen Levels verwendet werden.",
"Game Rules": "Spielregeln",
"levels": "Levels",
"tactics": "Taktiken",
"regular": "regulär",
"relaxed": "relaxed",
"none": "keine",
"Rules": "Regend"
}
+11
View File
@@ -0,0 +1,11 @@
{
"Lean Game Server": "Lean Game Server",
"<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>": "<p>Game rules determine if it is allowed to skip levels and if the games runs checks to only allow unlocked tactics and theorems in proofs.</p><1>Note: \"Unlocked\" tactics (or theorems) are determined by two things: The set of minimal tactics needed to solve a level, plus any tactics you unlocked in another level. That means if you unlock <1>simp</1> in a level, you can use it henceforth in any level.</1><p>The options are:</p>",
"Game Rules": "Game Rules",
"levels": "levels",
"tactics": "tactics",
"regular": "regular",
"relaxed": "relaxed",
"none": "none",
"Rules": "Rules"
}
+2 -1
View File
@@ -139,7 +139,7 @@ function LandingPage() {
"trequetrum/lean4game-logic",
]
let allTiles = allGames.map((gameId) => (useGetGameInfoQuery({game: `g/${gameId}`}).data?.tile))
const { t, i18n } = useTranslation();
const { t, i18n } = useTranslation()
return <div className="landing-page">
<header style={{backgroundImage: `url(${bgImage})`}}>
@@ -149,6 +149,7 @@ function LandingPage() {
<div>
<button onClick={() => i18n.changeLanguage("en")}>{flag["English"]}</button>
<button onClick={() => i18n.changeLanguage("fr")}>{flag["French"]}</button>
<button onClick={() => i18n.changeLanguage("de")}>{flag["German"]}</button>
{/* Add more buttons for other languages as needed */}
</div>
<div id="main-title">
+21 -16
View File
@@ -2,6 +2,7 @@
* @fileOverview
*/
import * as React from 'react'
import { Trans, useTranslation } from 'react-i18next'
/** Pop-up that is displayed when opening the help explaining the game rules.
*
@@ -9,42 +10,46 @@ import * as React from 'react'
* controlled by the containing element.
*/
export function RulesHelpPopup ({handleClose}: {handleClose: () => void}) {
const { t, i18n } = useTranslation()
return <div className="privacy-policy modal-wrapper">
<div className="modal-backdrop" onClick={handleClose} />
<div className="modal">
<div className="codicon codicon-close modal-close" onClick={handleClose}></div>
<h2>Game Rules</h2>
<p>
Game rules determine if it is allowed to skip levels and if the games runs checks to only
allow unlocked tactics and theorems in proofs.
</p>
<p>
Note: "Unlocked" tactics (or theorems) are determined by two things: The set of minimal
tactics needed to solve a level, plus any tactics you unlocked in another level. That means
if you unlock <code>simp</code> in a level, you can use it henceforth in any level.
</p>
<p>The options are:</p>
<h2>{t("Game Rules")}</h2>
<Trans>
<p>
Game rules determine if it is allowed to skip levels and if the games runs checks to only
allow unlocked tactics and theorems in proofs.
</p>
<p>
Note: "Unlocked" tactics (or theorems) are determined by two things: The set of minimal
tactics needed to solve a level, plus any tactics you unlocked in another level. That means
if you unlock <code>simp</code> in a level, you can use it henceforth in any level.
</p>
<p>The options are:</p>
</Trans>
<table>
<thead>
<tr>
<th scope="col"></th>
<th scope="col">levels</th>
<th scope="col">tactics</th>
<th scope="col">{t("levels")}</th>
<th scope="col">{t("tactics")}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">regular</th>
<th scope="row">{t("regular")}</th>
<td>🔐</td>
<td>🔐</td>
</tr>
<tr>
<th scope="row">relaxed</th>
<th scope="row">{t("relaxed")}</th>
<td>🔓</td>
<td>🔐</td>
</tr>
<tr>
<th scope="row">none</th>
<th scope="row">{t("none")}</th>
<td>🔓</td>
<td>🔓</td>
</tr>
+6 -4
View File
@@ -17,6 +17,7 @@ import { store } from '../state/store'
import '../css/world_tree.css'
import { PreferencesContext } from './infoview/context'
import { useTranslation } from 'react-i18next'
// Settings for the world tree
cytoscape.use( klay )
@@ -195,6 +196,7 @@ export const downloadFile = ({ data, fileName, fileType } :
/** The menu that is shown next to the world selection graph */
export function WorldSelectionMenu({rulesHelp, setRulesHelp}) {
const { t, i18n } = useTranslation()
const gameId = React.useContext(GameIdContext)
const difficulty = useSelector(selectDifficulty(gameId))
const dispatch = useAppDispatch()
@@ -202,20 +204,20 @@ export function WorldSelectionMenu({rulesHelp, setRulesHelp}) {
function label(x : number) {
return x == 0 ? 'none' : x == 1 ? 'relaxed' : 'regular'
return x == 0 ? t("none") : x == 1 ? t("relaxed") : t("regular")
}
return <nav className={`world-selection-menu${mobile ? '' : ' desktop'}`}>
<div className="slider-wrap">
<span className="difficulty-label">Rules
<span className="difficulty-label">{t("Rules")}
<FontAwesomeIcon icon={rulesHelp ? faXmark : faCircleQuestion} className='helpButton' onClick={() => (setRulesHelp(!rulesHelp))}/>
</span>
<Slider
orientation="vertical"
title="Game Rules"
title={t("Game Rules")}
min={0} max={2}
aria-label="Game Rules"
aria-label={t("Game Rules")}
value={difficulty}
marks={[
{value: 0, label: label(0)},
+2 -20
View File
@@ -1,29 +1,11 @@
import i18n from "i18next";
import Backend from "i18next-http-backend"
import { initReactI18next } from "react-i18next";
// the translations
// (tip move them in a JSON file and import them,
// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files)
const resources = {
en: {
translation: {
"Welcome to React": "Welcome to React and react-i18next",
"Lean Game Server": "Lean Game Server translated"
}
},
fr: {
translation: {
"Welcome to React": "Bienvenue à React et react-i18next",
"Lean Game Server": "Lean Game Server French"
}
}
};
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.use(Backend)
.init({
resources,
lng: "en", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
// if you're using a language detector, do not define the lng option