diff --git a/client/i18next-scanner.config.cjs b/client/i18next-scanner.config.cjs new file mode 100644 index 00000000..aa0c6ad3 --- /dev/null +++ b/client/i18next-scanner.config.cjs @@ -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.
) in translations instead of indexed keys. + keepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // Which nodes are allowed to be kept in translations during defaultValue generation of . + + // // 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(); + } + ), + +}; diff --git a/client/public/locales/de/translation.json b/client/public/locales/de/translation.json new file mode 100644 index 00000000..e7497d61 --- /dev/null +++ b/client/public/locales/de/translation.json @@ -0,0 +1,11 @@ +{ + "Lean Game Server": "Lean-Spieleserver", + "

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.

<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 in a level, you can use it henceforth in any level.

The options are:

": "

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.

<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 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" +} diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json new file mode 100644 index 00000000..d4e17215 --- /dev/null +++ b/client/public/locales/en/translation.json @@ -0,0 +1,11 @@ +{ + "Lean Game Server": "Lean Game Server", + "

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.

<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 in a level, you can use it henceforth in any level.

The options are:

": "

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.

<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 in a level, you can use it henceforth in any level.

The options are:

", + "Game Rules": "Game Rules", + "levels": "levels", + "tactics": "tactics", + "regular": "regular", + "relaxed": "relaxed", + "none": "none", + "Rules": "Rules" +} diff --git a/client/src/components/landing_page.tsx b/client/src/components/landing_page.tsx index 987e852f..e71d0f4d 100644 --- a/client/src/components/landing_page.tsx +++ b/client/src/components/landing_page.tsx @@ -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
@@ -149,6 +149,7 @@ function LandingPage() {
+ {/* Add more buttons for other languages as needed */}
diff --git a/client/src/components/popup/rules_help.tsx b/client/src/components/popup/rules_help.tsx index e4ed1cd4..ca85386e 100644 --- a/client/src/components/popup/rules_help.tsx +++ b/client/src/components/popup/rules_help.tsx @@ -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
-

Game Rules

-

- 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. -

-

- 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 simp in a level, you can use it henceforth in any level. -

-

The options are:

+

{t("Game Rules")}

+ +

+ 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. +

+

+ 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 simp in a level, you can use it henceforth in any level. +

+

The options are:

+
- - + + - + - + - + diff --git a/client/src/components/world_tree.tsx b/client/src/components/world_tree.tsx index 65fcc8ac..9fbe1004 100644 --- a/client/src/components/world_tree.tsx +++ b/client/src/components/world_tree.tsx @@ -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
levelstactics{t("levels")}{t("tactics")}
regular{t("regular")} 🔐 🔐
relaxed{t("relaxed")} 🔓 🔐
none{t("none")} 🔓 🔓