diff --git a/package-lock.json b/package-lock.json index 3137e1eb..0f60c8fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,8 @@ "d3-delaunay": "^6.0.4", "d3-fisheye": "^2.1.2", "gcode-toolpath": "^3.0.0", + "i18next": "^24.2.3", + "i18next-browser-languagedetector": "^8.0.4", "javascript-algorithms": "0.0.5", "kdbush": "^4.0.2", "konva": "^10.0.12", @@ -42,6 +44,7 @@ "react-dom": "^19.2.0", "react-error-boundary": "^6.0.0", "react-ga4": "^2.1.0", + "react-i18next": "^15.4.1", "react-icons": "^5.5.0", "react-konva": "^19.2.1", "react-redux": "^9.2.0", @@ -8855,6 +8858,14 @@ "dev": true, "license": "MIT" }, + "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==", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -8899,6 +8910,44 @@ "node": ">=10.17.0" } }, + "node_modules/i18next": { + "version": "24.2.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz", + "integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==", + "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" + } + ], + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -12036,6 +12085,31 @@ "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" }, + "node_modules/react-i18next": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.4.tgz", + "integrity": "sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.4.0", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -13747,7 +13821,7 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, + "devOptional": true, "peer": true, "bin": { "tsc": "bin/tsc", @@ -14145,6 +14219,14 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 7730c464..2ebe95e8 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "d3-delaunay": "^6.0.4", "d3-fisheye": "^2.1.2", "gcode-toolpath": "^3.0.0", + "i18next": "^24.2.3", + "i18next-browser-languagedetector": "^8.0.4", "javascript-algorithms": "0.0.5", "kdbush": "^4.0.2", "konva": "^10.0.12", @@ -38,6 +40,7 @@ "react-dom": "^19.2.0", "react-error-boundary": "^6.0.0", "react-ga4": "^2.1.0", + "react-i18next": "^15.4.1", "react-icons": "^5.5.0", "react-konva": "^19.2.1", "react-redux": "^9.2.0", diff --git a/src/components/CheckboxOption.js b/src/components/CheckboxOption.js index 409a5e54..fb26d158 100644 --- a/src/components/CheckboxOption.js +++ b/src/components/CheckboxOption.js @@ -1,4 +1,5 @@ import React from "react" +import { useTranslation } from "react-i18next" import Col from "react-bootstrap/Col" import Row from "react-bootstrap/Row" import Form from "react-bootstrap/Form" @@ -13,6 +14,7 @@ const CheckboxOption = ({ onChange, label = true, }) => { + const { t } = useTranslation() const option = options[optionKey] const visible = option.isVisible === undefined ? true : option.isVisible(model, data) @@ -36,7 +38,7 @@ const CheckboxOption = ({ htmlFor="options-step" className="mb-0" > - {option.title} + {t(option.title)} )} diff --git a/src/components/DropdownOption.js b/src/components/DropdownOption.js index 882ef3af..043b357d 100644 --- a/src/components/DropdownOption.js +++ b/src/components/DropdownOption.js @@ -1,6 +1,7 @@ /* global document */ import React from "react" +import { useTranslation } from "react-i18next" import Col from "react-bootstrap/Col" import Row from "react-bootstrap/Row" import Form from "react-bootstrap/Form" @@ -14,6 +15,7 @@ const DropdownOption = ({ onChange, index, }) => { + const { t } = useTranslation() const option = options[optionKey] const currentChoice = data[optionKey] @@ -24,10 +26,10 @@ const DropdownOption = ({ choices = Array.isArray(choices) ? choices.map((choice) => { - return { value: choice, label: choice } + return { value: choice, label: t(choice) } }) : Object.keys(choices).map((key) => { - return { value: key, label: choices[key] } + return { value: key, label: t(choices[key]) } }) const currentLabel = ( choices.find((choice) => choice.value == currentChoice) || choices[0] @@ -57,7 +59,7 @@ const DropdownOption = ({ className="m-0" htmlFor="options-dropdown" > - {option.title} + {t(option.title)} diff --git a/src/components/InputOption.js b/src/components/InputOption.js index 32819bdb..91466789 100644 --- a/src/components/InputOption.js +++ b/src/components/InputOption.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from "react" +import { useTranslation } from "react-i18next" import Col from "react-bootstrap/Col" import Row from "react-bootstrap/Row" import Form from "react-bootstrap/Form" @@ -16,6 +17,7 @@ const InputOption = ({ label = true, }) => { inputRef ||= useRef() + const { t } = useTranslation() const [value, setValue] = useState(data[optionKey]) const shiftKeyPressed = useKeyPress("Shift", inputRef) @@ -109,7 +111,7 @@ const InputOption = ({ htmlFor={`option-${optionKey}`} className="mb-0" > - {title} + {t(title)} )} @@ -130,7 +132,7 @@ const InputOption = ({ className="me-2 mb-0" style={{ width: "22px" }} > - {title} + {t(title)} )} {renderedInput} diff --git a/src/components/QuadrantButtonsOption.js b/src/components/QuadrantButtonsOption.js index 9cf3b4ba..0c3396d8 100644 --- a/src/components/QuadrantButtonsOption.js +++ b/src/components/QuadrantButtonsOption.js @@ -1,4 +1,5 @@ import React from "react" +import { useTranslation } from "react-i18next" import Col from "react-bootstrap/Col" import Row from "react-bootstrap/Row" import Form from "react-bootstrap/Form" @@ -6,6 +7,7 @@ import ToggleButton from "react-bootstrap/ToggleButton" import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup" const QuadrantButtonsOption = (props) => { + const { t } = useTranslation() const option = props.options[props.optionKey] const { data } = props const value = data[props.optionKey] @@ -22,7 +24,7 @@ const QuadrantButtonsOption = (props) => { sm={5} className="mb-1" > - {option.title} + {t(option.title)} { className="px-4" style={{ borderRadius: 0 }} > - upper left + {t("upper left")} { className="px-4" style={{ borderRadius: 0 }} > - upper right + {t("upper right")} { className="px-4" style={{ borderRadius: 0 }} > - lower left + {t("lower left")} { className="px-4" style={{ borderRadius: 0 }} > - lower right + {t("lower right")} diff --git a/src/components/SliderOption.js b/src/components/SliderOption.js index 797f779d..92fb8a47 100644 --- a/src/components/SliderOption.js +++ b/src/components/SliderOption.js @@ -1,5 +1,6 @@ import Slider from "rc-slider" import React, { useState, useEffect } from "react" +import { useTranslation } from "react-i18next" import Col from "react-bootstrap/Col" import Row from "react-bootstrap/Row" import Form from "react-bootstrap/Form" @@ -12,6 +13,7 @@ const SliderOption = ({ model, label = true, }) => { + const { t } = useTranslation() const [value, setValue] = useState(data[optionKey]) useEffect(() => { @@ -114,7 +116,7 @@ const SliderOption = ({ htmlFor={`option-${optionKey}`} className="mb-0" > - {title} + {t(title)} )} diff --git a/src/components/ToggleButtonOption.js b/src/components/ToggleButtonOption.js index 6f45cf1c..d304050a 100644 --- a/src/components/ToggleButtonOption.js +++ b/src/components/ToggleButtonOption.js @@ -1,4 +1,5 @@ import React from "react" +import { useTranslation } from "react-i18next" import Col from "react-bootstrap/Col" import Row from "react-bootstrap/Row" import Form from "react-bootstrap/Form" @@ -6,6 +7,7 @@ import ToggleButton from "react-bootstrap/ToggleButton" import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup" const ToggleButtonOption = (props) => { + const { t } = useTranslation() const option = props.options[props.optionKey] const { data } = props const model = props.model || data @@ -27,7 +29,7 @@ const ToggleButtonOption = (props) => { return ( - {option.title} + {t(option.title)} @@ -48,7 +50,7 @@ const ToggleButtonOption = (props) => { variant="light" value={choice} > - {choice} + {t(choice)} ) })} diff --git a/src/features/app/About.js b/src/features/app/About.js index c9581540..8762b7d2 100644 --- a/src/features/app/About.js +++ b/src/features/app/About.js @@ -1,4 +1,5 @@ import React from "react" +import { useTranslation } from "react-i18next" import Container from "react-bootstrap/Container" import Row from "react-bootstrap/Row" import Col from "react-bootstrap/Col" @@ -9,6 +10,8 @@ import { SANDIFY_VERSION } from "@/features/app/appSlice" import "./About.scss" const About = () => { + const { t } = useTranslation() + return (