diff --git a/package-lock.json b/package-lock.json index 4e2b670..0a07278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,12 @@ "@mui/icons-material": "^7.1.0", "@mui/material": "^7.1.0", "d3": "^7.9.0", + "i18next": "^26.0.3", + "i18next-browser-languagedetector": "^8.2.1", "lodash": "^4.17.21", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-i18next": "^17.0.2", "uplot": "^1.6.32", "uplot-react": "^1.2.2" }, @@ -283,9 +286,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", - "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3227,6 +3230,55 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "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==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "26.0.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.3.tgz", + "integrity": "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", + "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", @@ -3819,6 +3871,33 @@ "react": "^19.1.1" } }, + "node_modules/react-i18next": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.2.tgz", + "integrity": "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 26.0.1", + "react": ">= 16.8.0", + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", @@ -4215,6 +4294,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", @@ -4386,6 +4474,15 @@ } } }, + "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", diff --git a/package.json b/package.json index ce7b42c..49662fb 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,12 @@ "@mui/icons-material": "^7.1.0", "@mui/material": "^7.1.0", "d3": "^7.9.0", + "i18next": "^26.0.3", + "i18next-browser-languagedetector": "^8.2.1", "lodash": "^4.17.21", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-i18next": "^17.0.2", "uplot": "^1.6.32", "uplot-react": "^1.2.2" }, diff --git a/src/App.jsx b/src/App.jsx index fc86fe1..0fb4516 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,6 @@ /* global gtag */ import { useState, useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Link from "@mui/material/Link"; @@ -54,6 +55,7 @@ var [stateInURL, defaultCircuit, urlContainsState] = updateObjectFromUrl(initial console.log("stateInURL", stateInURL, defaultCircuit, urlContainsState); function App() { + const { t, i18n } = useTranslation(); const [userCircuit, setUserCircuit] = useState(defaultCircuit); const [settings, setSettings] = useState(stateInURL); const [urlSnackbar, setUrlSnackbar] = useState(false); @@ -99,7 +101,7 @@ function App() { message="This Snackbar will be dismissed in 5 seconds." > clearTimeout(timer); }, []); + useEffect(() => { + document.title = t("meta.pageTitle"); + const meta = document.querySelector('meta[name="description"]'); + if (meta) meta.setAttribute("content", t("meta.pageDescription")); + }, [i18n.language, t]); + return ( - - Smith charts can help you design matching networks and obtain maximum power transfer between your source and load. Plot s-parameters. - + {t("app.intro")} @@ -178,10 +184,8 @@ function App() { {sParamIndex !== -1 && ( setPlotType(newP)}> - Plot Raw S-Parameter Data - - {s1pIndex !== -1 ? "Plot Reflection Coefficient Looking Into DP1" : "Plot System Gain & Noise"} - + {t("app.plotSparam")} + {s1pIndex !== -1 ? t("app.plotImpedanceS1p") : t("app.plotImpedanceS2p")} )} @@ -225,14 +229,14 @@ function App() {
- Comment section below + {t("app.commentsTitle")} gtag("event", "click_microwave_maser")} target="_blank" color="inherit" > - Get professional support from Microwave Master here + {t("app.supportLink")}
{!import.meta.env.DEV && } diff --git a/src/Circuit.jsx b/src/Circuit.jsx index adba9cc..db311d7 100644 --- a/src/Circuit.jsx +++ b/src/Circuit.jsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { useTranslation } from "react-i18next"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; @@ -103,6 +104,7 @@ function toEngineeringNotation(num) { } function CustomComponent({ modalOpen, setModalOpen, value, index, setUserCircuit, frequency, interpolation }) { + const { t } = useTranslation(); const textInput = Object.keys(value) .map((x) => `${toEngineeringNotation(x)}, ${value[x].real}, ${value[x].imaginary}`) .join("\n"); @@ -110,8 +112,7 @@ function CustomComponent({ modalOpen, setModalOpen, value, index, setUserCircuit const [customInput, setCustomInput] = useState(textInput); const validCheckerResults = checkCustomZValid(customInput); - const helperText = - "If the textbox contains a comma it's assumed your data is comma separated, otherwise assumes whitespace separated. Each line must have 3 non-blank numberical values. The only accepted characters are 0-9, '-', '+', '.', e, E and ','. Frequency must be increasing"; + const helperText = t("circuit.custom.helperText"); var objResult = {}; if (validCheckerResults[0]) { @@ -131,23 +132,23 @@ function CustomComponent({ modalOpen, setModalOpen, value, index, setUserCircuit setModalOpen((o) => !o); }} > - Set Impedance vs Frequency + {t("circuit.custom.setZf")} - Custom Impedance input box + {t("circuit.custom.modalTitle")} - Enter rows of impedance vs increasing frequency + {t("circuit.custom.desc1")}
- Don't enter units or characters; use 2440e6 notation for 2440MHz + {t("circuit.custom.desc2")}
- comma separated: FREQUENCY, REAL, IMAGINARY + {t("circuit.custom.desc3")}
- whitespace separated: FREQUENCY REAL IMAGINARY + {t("circuit.custom.desc4")}
- } label="Sample & Hold" /> - } label="Linear Interpolation" /> + } label={t("circuit.custom.sampleHold")} /> + } label={t("circuit.custom.linear")} />
@@ -206,6 +207,7 @@ function CustomComponent({ modalOpen, setModalOpen, value, index, setUserCircuit } function SparamComponent({ modalOpen, setModalOpen, value, index, setUserCircuit, setPlotType, setSettings, frequency }) { + const { t } = useTranslation(); const [customInput, setCustomInput] = useState(value.raw ? value.raw : ""); const [showAllData, setShowAllData] = useState(false); const allcols = ["S11", "S21", "S12", "S22"]; @@ -215,11 +217,12 @@ function SparamComponent({ modalOpen, setModalOpen, value, index, setUserCircuit const validCheckerResults = parsed.error === null; const numRows = Object.keys(parsed.data).length; const defaultMaxRows = 300; - const helperText = customInput == "" ? "Copy in a file" : validCheckerResults ? `${numRows} data points parsed succesfully` : parsed.error; + const helperText = + customInput == "" ? t("circuit.sparam.copyFile") : validCheckerResults ? t("circuit.sparam.pointsParsed", { n: numRows }) : parsed.error; return ( <> - .{value.type} ~ GS0 = {gs0.toPrecision(3)}dB + {t("circuit.sparam.gs0", { type: value.type, v: gs0.toPrecision(3) })} - Paste .s1p or .s2p file contents below + {t("circuit.sparam.pasteTitle")} setCustomInput(s2pExample)} sx={{ cursor: "pointer", px: 1 }}> - s2p example + {t("circuit.sparam.s2pExample")} setCustomInput(s1pExample)} sx={{ cursor: "pointer", px: 1 }}> - s1p example + {t("circuit.sparam.s1pExample")} - {customInput.length} characters{customInput.length > 1000 ? ": 1K max for URL saving" : ""} + {t("circuit.sparam.chars", { n: customInput.length })} + {customInput.length > 1000 ? t("circuit.sparam.urlLimit") : ""}
    -
  • File type: .{parsed.type}
  • -
  • Frequency Unit: {parsed.settings.freq_unit}
  • -
  • Data Type: {parsed.settings.param}
  • +
  • {t("circuit.sparam.fileType", { type: parsed.type })}
  • +
  • {t("circuit.sparam.freqUnit", { u: parsed.settings.freq_unit })}
  • +
  • {t("circuit.sparam.dataType", { u: parsed.settings.param })}
  • - Format: {parsed.settings.format} ({parsed.settings.format == "RI" ? "Rectangular" : "Polar"}) + {t("circuit.sparam.format", { + f: parsed.settings.format, + extra: parsed.settings.format == "RI" ? t("circuit.sparam.rectangular") : t("circuit.sparam.polar"), + })}
  • -
  • Zo: {parsed.settings.zo}
  • +
  • {t("circuit.sparam.zo", { v: parsed.settings.zo })}
- ABOVE TEXT FORMATTED INTO A TABLE{" "} - {numRows > defaultMaxRows && (showAllData ? "- All rows of data: " : `- First ${Math.min(defaultMaxRows, numRows)} rows of data: `)} - {numRows > defaultMaxRows && } + {t("circuit.sparam.tableIntro")}{" "} + {numRows > defaultMaxRows && + (showAllData ? t("circuit.sparam.rowsAll") : t("circuit.sparam.rowsFirst", { n: Math.min(defaultMaxRows, numRows) }))} + {numRows > defaultMaxRows && ( + + )} - Frequency ({parsed.settings.freq_unit}) + {t("circuit.sparam.freqCol", { u: parsed.settings.freq_unit })} {allcols.map((column) => { if (!(column in Object.values(parsed.data)[0])) return null; return [ @@ -349,12 +361,12 @@ function SparamComponent({ modalOpen, setModalOpen, value, index, setUserCircuit
- Noise Data - note that noise frequencies not in s-param are discarded + {t("circuit.sparam.noiseTitle")} - Frequency ({parsed.settings.freq_unit}) + {t("circuit.sparam.freqCol", { u: parsed.settings.freq_unit })} NFmin |GAMMAopt| ∠GAMMAopt @@ -386,6 +398,7 @@ function SparamComponent({ modalOpen, setModalOpen, value, index, setUserCircuit } function ImpedanceComponent({ real, imaginary, zToVal }) { + const { t } = useTranslation(); const [editOpen, setEditOpen] = useState(false); const [editValue, setEditValue] = useState(); @@ -403,13 +416,13 @@ function ImpedanceComponent({ real, imaginary, zToVal }) { <> - Z = + {t("circuit.impedance.zEquals")} {zStr} {hasZToVal && ( - + setEditOpen(false)} fullWidth> - Enter impedance + {t("circuit.impedance.enterZTitle")} setEditValue(e.target.value)} - helperText="Enter numeric impedance (will be passed to the component calculator)" + helperText={t("circuit.impedance.enterZHelper")} /> @@ -489,11 +502,12 @@ function SliderAdjust({ handleChange, value, baseValue, unit }) { } function ComplexComponent({ real, imaginary, index, setUserCircuit, slider_re, slider_im }) { + const { t } = useTranslation(); return ( <> + @@ -550,13 +565,13 @@ function TransformerComponent({ l1, unit_l1, l2, unit_l2, k, model, index, setUs } - label="Coupled-Inductor Model" + label={t("circuit.transformer.coupled")} sx={{ "& .MuiFormControlLabel-label": { fontSize: "0.7rem", lineHeight: 1 }, my: 0, mr: 1 }} /> } - label="Ideal Transformer" + label={t("circuit.transformer.ideal")} sx={{ "& .MuiFormControlLabel-label": { fontSize: "0.7rem", lineHeight: 1 }, my: 0 }} /> @@ -564,7 +579,7 @@ function TransformerComponent({ l1, unit_l1, l2, unit_l2, k, model, index, setUs <> - 1 : + {t("circuit.transformer.ratioPrefix")} - length = {length} + {t("common.length")} = {length} setValue(v, "slider", setUserCircuit, index)} value={slider} baseValue={value} unit={unit} /> setValue(e.target.value, "zo", setUserCircuit, index)} /> setValue(e.target.value, "eeff", setUserCircuit, index)} - helperText={eeff != 1 && (unit == "λ" || unit == "deg") ? "Note - physical line length is changed" : ""} + helperText={eeff != 1 && (unit == "λ" || unit == "deg") ? t("circuit.labels.eeffHelper") : ""} /> ); @@ -758,11 +775,12 @@ function EsComponent({ type, value, setUserCircuit, index, showIdeal }) { } function ResistorComponent({ value, unit, index, setUserCircuit, slider }) { + const { t } = useTranslation(); return ( <> - Q Factor = {((component.value * 2 * Math.PI * frequency * unitConverter[component.unit]) / component.esr).toPrecision(3)} + {t("circuit.labels.qFactorEquals", { + v: ((component.value * 2 * Math.PI * frequency * unitConverter[component.unit]) / component.esr).toPrecision(3), + })} ); case "esl": @@ -1047,7 +1071,7 @@ function Circuit({ userCircuit, setUserCircuit, frequency, setPlotType, setSetti {Object.keys(circuitComponents).map((k, i) => { const c = circuitComponents[k]; if ("unselectable" in c) return null; - else if (c.name == "S-Parameter" && sParamIndex !== -1) + else if (k === "sparam" && sParamIndex !== -1) return null; //only one s-parameter component allowed else return ( @@ -1083,7 +1107,7 @@ function Circuit({ userCircuit, setUserCircuit, frequency, setPlotType, setSetti > - {c.name} + {t(`circuit.components.${k}`)} @@ -1092,17 +1116,18 @@ function Circuit({ userCircuit, setUserCircuit, frequency, setPlotType, setSetti setShortedStubDialogOpen(false)}> - - Shorted-stub arc starts at 0-ohms (length = 0), therefore will not be connected to the previous point. This may seem unintuitive but makes - more sense once considering frequency-span and tolerance - + {t("circuit.shortedStubDialog")}
-

Click components above to add them to the circuit below. Impedance is looking {s1pIndex === -1 ? "towards the BLACK BOX" : "into DP1"}

+

+ {t("circuit.hint", { + direction: s1pIndex === -1 ? t("circuit.towardsBlackBox") : t("circuit.intoDp1"), + })} +

@@ -1192,7 +1217,7 @@ function Circuit({ userCircuit, setUserCircuit, frequency, setPlotType, setSetti color: { color }, }} > - DP{i} + {t("circuit.dp", { i })}
{comp.circuitInputs.map((input) => componentMap(input, c, i))} diff --git a/src/Equations.jsx b/src/Equations.jsx index 64f15be..14800c8 100644 --- a/src/Equations.jsx +++ b/src/Equations.jsx @@ -11,6 +11,7 @@ import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; +import { useTranslation } from "react-i18next"; const asciiArtTransformer = ` --- (L1-Lm) --- --- (L2-Lm) --- <- look this way @@ -20,11 +21,12 @@ Zl Lm `; function Equations() { + const { t } = useTranslation(); return ( <> } aria-controls="panel1-content" id="panel1-header"> - Equations used by this site + {t("equations.title")}
- Item - Equation - Notes + {t("equations.item")} + {t("equations.equation")} + {t("equations.notes")} diff --git a/src/Footer.jsx b/src/Footer.jsx index e6a5448..d548bdb 100644 --- a/src/Footer.jsx +++ b/src/Footer.jsx @@ -1,3 +1,4 @@ +import { useTranslation } from "react-i18next"; import AppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; import Toolbar from "@mui/material/Toolbar"; @@ -7,16 +8,14 @@ import GitHubIcon from "@mui/icons-material/GitHub"; import Link from "@mui/material/Link"; function Footer() { + const { t } = useTranslation(); return ( - - © {new Date().getFullYear()} Will Kelsey. This work is licensed under a Creative Commons Attribution 4.0 International License. You may - not resell this tool - - v2.0 + {t("footer.license", { year: new Date().getFullYear() })} + {t("footer.version")}
- + { const svg = svgRef.current; // Serialize the SVG to a string @@ -780,7 +782,7 @@ function Graph({ {sParameters && sParameters.type === "s2p" && (
setShowStabilityPlot(!showStabilityPlot)} /> - +
)} {Object.keys(showSPlots).map((s) => { @@ -797,11 +799,11 @@ function Graph({ })}
setShowZPlots(!showZPlots)} /> - +
{nonIdealUsed >= 0 && ( - + - Show Ideal + {t("graph.showIdeal")} )} @@ -827,7 +829,7 @@ function Graph({ right: 4, }} > - Graph Settings + {t("graph.graphSettings")} - Graph Settings + {t("graph.dialogTitle")} setShowAdmittance(e.target.checked)} />} - label="Show Admittance" + label={t("graph.showAdmittance")} /> @@ -928,24 +931,17 @@ function DialogGraphSettings({ } function HoverTooltip({ z, frequency, zo, freqUnit }) { - if (z.real < 0) return

Move cursor back inside the circle

; + const { t } = useTranslation(); + if (z.real < 0) return

{t("graph.hoverOutside")}

; var res = processImpedance(z, zo); return ( <> - {frequency && ( -

- Frequency = {frequency / unitConverter[freqUnit]} {freqUnit} -

- )} -

- Impedance = {res.zStr} ({res.zPolarStr}) -

-

Admittance = {res.admString}

-

- Refl-Coeff = {res.refStr} ({res.refPolarStr}) -

-

VSWR = {res.vswr}

-

Q-Factor = {res.qFactor}

+ {frequency &&

{t("graph.frequency", { v: frequency / unitConverter[freqUnit], unit: freqUnit })}

} +

{t("graph.impedance", { z: res.zStr, polar: res.zPolarStr })}

+

{t("graph.admittance", { v: res.admString })}

+

{t("graph.reflCoeff", { v: res.refStr, polar: res.refPolarStr })}

+

{t("graph.vswr", { v: res.vswr })}

+

{t("graph.qFactorHover", { v: res.qFactor })}

); } diff --git a/src/LanguageSwitcher.jsx b/src/LanguageSwitcher.jsx new file mode 100644 index 0000000..4d0ccc5 --- /dev/null +++ b/src/LanguageSwitcher.jsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import ToggleButton from "@mui/material/ToggleButton"; +import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; +import Tooltip from "@mui/material/Tooltip"; +import { supportedLanguages } from "./i18n.js"; + +export default function LanguageSwitcher() { + const { i18n, t } = useTranslation(); + const current = supportedLanguages.some((l) => l.code === i18n.language) ? i18n.language : "en"; + + return ( + + v && i18n.changeLanguage(v)} + sx={{ + "& .MuiToggleButton-root": { + color: "rgb(184, 255, 241)", + borderColor: "rgba(184, 255, 241, 0.5)", + py: 0.25, + px: 1, + fontSize: "0.75rem", + }, + "& .Mui-selected": { + backgroundColor: "rgba(184, 255, 241, 0.2) !important", + color: "rgb(184, 255, 241)", + }, + }} + > + {supportedLanguages.map(({ code }) => ( + + {code.toUpperCase()} + + ))} + + + ); +} diff --git a/src/NavBar.jsx b/src/NavBar.jsx index a03e8c3..8cc3449 100644 --- a/src/NavBar.jsx +++ b/src/NavBar.jsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { useTranslation } from "react-i18next"; import AppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; import Toolbar from "@mui/material/Toolbar"; @@ -18,7 +19,10 @@ import { theme } from "./commonFunctions.js"; // import your theme import SmithChartSvg from "./assets/smith-chart-icon.svg"; // import your SVG file import Home from "./assets/home.svg"; // import your SVG file +import LanguageSwitcher from "./LanguageSwitcher.jsx"; + function NavBar() { + const { t } = useTranslation(); const [urlSnackbar, setUrlSnackbar] = useState(false); return ( @@ -31,7 +35,7 @@ function NavBar() { message="This Snackbar will be dismissed in 5 seconds." > Smith Chart - ONLINE SMITH CHART TOOL + {t("nav.title")}
- + + { @@ -63,9 +68,9 @@ function NavBar() { - + { window.location.href = window.location.origin; @@ -81,10 +86,10 @@ function NavBar() { home
- Version - Date - Notes + {t("release.version")} + {t("release.date")} + {t("release.notes")} @@ -44,11 +48,7 @@ function ReleaseNotes() { August 2025 -
    -
  • Added s-parameter component
  • -
  • Added gain circles for turning s2p gain
  • -
  • Added a couple of s-parameter tutorials
  • -
+
    {Array.isArray(v21) && v21.map((item, i) =>
  • {item}
  • )}
@@ -60,24 +60,11 @@ function ReleaseNotes() {
    -
  • Site moved from will-kelsey.com/smith_chart/ to onlinesmithchart.com
  • -
  • - Whole site re-written. Over 6 years many features were added to the old site which left the code very messy. Some users were - looking at the code to verify implementations, which was tough. Now, the code is much more organised and optimized. In the - future I hope the community can add features and improvements to the codebase. -
  • -
  • - Moved from vanilla JS to React + MUI. This allows: running lint, more maintainable code, smaller file size, many micro-benefits - from joining the mainstream -
  • -
  • As well as a re-write, the following new features are added:
  • -
      -
    • Smith chart is interactive - hover over the chart. This makes it possible to see Z when there are N curves (tol, fspan)
    • -
    • Components have sliders - quickly see whether to increase or decrease component values
    • -
    • Add Noise Figure circles
    • -
    • Add transformer component
    • -
    • Save whole state in the URL
    • -
    +
  • {t("release.v20a")}
  • +
  • {t("release.v20b")}
  • +
  • {t("release.v20c")}
  • +
  • {t("release.v20d")}
  • +
      {Array.isArray(v20features) && v20features.map((item, i) =>
    • {item}
    • )}
@@ -89,20 +76,11 @@ function ReleaseNotes() { May 2018 - Adding a brief history of the site -

- I needed to match a Maxim 2.4GHz bluetooth amplifier (with a 25+25j output impedace) to a 50ohm chip antenna. The only software I - could find was from Fritz Dellsperger however on Windows 11 the GUI became unusable. -

-

- The first version of this site was extremely simple to support my basic needs; a black box, capacitor, inductor and smith chart - diagram. We successfully used the tool to chose our component values. -

-

The community has made hundreds of requests over the years and many features have been added

-

- All features have been verified against Fritz's software, YouTube videos, allaboutcircuits.com, etc. Of coursethere were some - mistakes, most of these have been identified and fixed during the 100's of comments -

+ {t("release.v10italic")} +

{t("release.v10a")}

+

{t("release.v10b")}

+

{t("release.v10c")}

+

{t("release.v10d")}

diff --git a/src/Results.jsx b/src/Results.jsx index 9f6ce72..510120c 100644 --- a/src/Results.jsx +++ b/src/Results.jsx @@ -3,6 +3,7 @@ import Tooltip from "@mui/material/Tooltip"; import "uplot/dist/uPlot.min.css"; import UplotReact from "uplot-react"; import { useState, useRef, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { processImpedance, rectangularToPolar, unitConverter } from "./commonFunctions"; @@ -190,24 +191,55 @@ const optionsGainInit = { // }); // } -function renderChart_new(setCommon, containerRef, freqUnit) { +function renderChart_new(setCommon, containerRef, freqUnit, t) { + const freqLabel = t("results.frequencyAxis", { unit: freqUnit }); setCommon((o) => { return { ...o, width: containerRef.current.offsetWidth, series: o.series.map((s, i) => { - if (i === 0) return { ...s, label: `Frequency (${freqUnit})` }; + if (i === 0) return { ...s, label: freqLabel }; return s; }), axes: o.axes.map((a, i) => { - if (i === 0) return { ...a, label: `Frequency (${freqUnit})` }; + if (i === 0) return { ...a, label: freqLabel }; return a; }), }; }); } +function localizedOptionsInit(t) { + return { + ...optionsInit, + series: [ + { ...optionsInit.series[0], label: t("results.s11db") }, + { ...optionsInit.series[1], label: t("results.s11ang") }, + ], + axes: [ + { ...optionsInit.axes[0], label: t("results.s11db") }, + { ...optionsInit.axes[1], label: t("results.s11ang") }, + ], + }; +} + +function localizedOptions2Init(t) { + return { + ...options2Init, + series: [{ ...options2Init.series[0], label: t("results.s21db") }], + axes: [{ ...options2Init.axes[0], label: t("results.s21db") }], + }; +} + +function localizedOptionsGainInit(t) { + return { + ...optionsGainInit, + axes: [{ ...optionsGainInit.axes[0], label: t("results.gainAxis") }], + }; +} + function SPlot({ sparametersData, options, freqUnit, title }) { + const { t } = useTranslation(); if (!sparametersData || sparametersData.length === 0) return null; return ["S11", "S12", "S21", "S22"].map((s) => { if (!(s in Object.values(sparametersData)[0])) return null; @@ -228,7 +260,7 @@ function SPlot({ sparametersData, options, freqUnit, title }) { return (
- {title}: {s} magnitude and angle + {title}: {t("results.sMagPhase", { s })}
@@ -236,6 +268,7 @@ function SPlot({ sparametersData, options, freqUnit, title }) { }); } function GainPlot({ gain, options, freqUnit, title, legend }) { + const { t } = useTranslation(); if (!gain || Object.keys(gain).length === 0) return null; const sParamOpt = JSON.parse(JSON.stringify(options)); sParamOpt.axes[1].label = legend; @@ -247,7 +280,7 @@ function GainPlot({ gain, options, freqUnit, title, legend }) { } sData.push(m); sParamOpt.series.push({ - label: i == gain.length - 1 ? legend : `tol ${i}`, + label: i == gain.length - 1 ? legend : t("results.tol", { i }), stroke: i == gain.length - 1 ? "blue" : "gray", width: 2, scale: "y", @@ -263,6 +296,7 @@ function GainPlot({ gain, options, freqUnit, title, legend }) { ); } function RPlot({ RefIn, options, freqUnit, title }) { + const { t } = useTranslation(); if (!RefIn || Object.keys(RefIn).length === 0) return null; const sParamOpt = JSON.parse(JSON.stringify(options)); // const f = []; @@ -277,13 +311,13 @@ function RPlot({ RefIn, options, freqUnit, title }) { plotData.push(m, a); sParamOpt.series.push( { - label: i == RefIn.length - 1 ? "|S11| (dB)" : `|tol${i}|`, + label: i == RefIn.length - 1 ? t("results.s11db") : t("results.tolPipe", { i }), stroke: i == RefIn.length - 1 ? "blue" : "#4b4c80", width: 2, scale: "y", }, { - label: i == RefIn.length - 1 ? "∠S11 (°)" : `∠tol${i}`, + label: i == RefIn.length - 1 ? t("results.s11ang") : t("results.tolAng", { i }), stroke: i == RefIn.length - 1 ? "red" : "#9c5656", width: 2, scale: "y2", @@ -299,25 +333,30 @@ function RPlot({ RefIn, options, freqUnit, title }) { } export default function Results({ zProc, spanResults, freqUnit, plotType, sParameters, gainResults, noiseArray, RefIn, zo }) { + const { t, i18n } = useTranslation(); const { zStr, zPolarStr, refStr, refPolarStr, vswr, qFactor } = zProc; const containerRef = useRef(); // const [options, setOptions] = useState(optionsInit); // const [options2, setOptions2] = useState(options2Init); const [commonOptions, setCommonOptions] = useState(commonOptionsInit); + const loc1 = localizedOptionsInit(t); + const loc2 = localizedOptions2Init(t); + const locG = localizedOptionsGainInit(t); + const options3 = { width: commonOptions.width, height: commonOptions.height, - series: [...commonOptions.series, ...options2Init.series], - axes: [...commonOptions.axes, ...options2Init.axes], + series: [...commonOptions.series, ...loc2.series], + axes: [...commonOptions.axes, ...loc2.axes], scales: options2Init.scales, }; const options4 = { width: commonOptions.width, height: commonOptions.height, - series: [...commonOptions.series, ...optionsInit.series], - axes: [...commonOptions.axes, ...optionsInit.axes], + series: [...commonOptions.series, ...loc1.series], + axes: [...commonOptions.axes, ...loc1.axes], scales: optionsInit.scales, }; @@ -325,7 +364,7 @@ export default function Results({ zProc, spanResults, freqUnit, plotType, sParam width: commonOptions.width, height: commonOptions.height, series: [...commonOptions.series, ...optionsGainInit.series], - axes: [...commonOptions.axes, ...optionsGainInit.axes], + axes: [...commonOptions.axes, ...locG.axes], scales: optionsGainInit.scales, }; @@ -333,7 +372,7 @@ export default function Results({ zProc, spanResults, freqUnit, plotType, sParam width: commonOptions.width, height: commonOptions.height, series: commonOptions.series, - axes: [...commonOptions.axes, ...optionsInit.axes], + axes: [...commonOptions.axes, ...loc1.axes], scales: optionsInit.scales, }; @@ -385,22 +424,21 @@ export default function Results({ zProc, spanResults, freqUnit, plotType, sParam useEffect(() => { function handleResize() { - // renderChart(setOptions, setOptions2, containerRef, freqUnit); - renderChart_new(setCommonOptions, containerRef, freqUnit); + renderChart_new(setCommonOptions, containerRef, freqUnit, t); } handleResize(); window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; - }, [freqUnit]); + }, [freqUnit, t, i18n.language]); // plot s-parameters straight from the file if (plotType === "sparam" && sParameters !== null) { const sparametersData = sParameters.data; return (
- +
); @@ -408,38 +446,38 @@ export default function Results({ zProc, spanResults, freqUnit, plotType, sParam } else if (plotType !== "sparam" && sParameters !== null) { return (
- - - + + +
); } else return ( <> - Final Results + {t("results.finalResults")} - + - + - + - +
- (assuming{" "} + {t("results.assuming")}{" "} S11 2 + S21 @@ -450,11 +488,12 @@ export default function Results({ zProc, spanResults, freqUnit, plotType, sParam
    +
  • {t("results.maxS21", { v: maxS21.toPrecision(6), f: maxF, unit: freqUnit })}
  • - Max S21: {maxS21.toPrecision(6)} dB at {maxF} {freqUnit} -
  • -
  • - S21 3dB bandwidth: {db3_l == -1 || db3_m == -1 ? "N/A" : (db3_m - db3_l).toPrecision(6)} {freqUnit} + {t("results.bw3db", { + v: db3_l == -1 || db3_m == -1 ? t("results.na") : (db3_m - db3_l).toPrecision(6), + unit: freqUnit, + })}
diff --git a/src/Settings.jsx b/src/Settings.jsx index e2fe82b..2be0cd1 100644 --- a/src/Settings.jsx +++ b/src/Settings.jsx @@ -16,6 +16,7 @@ import IconButton from "@mui/material/IconButton"; import DeleteIcon from "@mui/icons-material/Delete"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { frequencyUnits, parseInput, polarToRectangular, rectangularToPolar, unitConverter } from "./commonFunctions"; @@ -36,6 +37,7 @@ function setUnit(value, field, setX) { } function DisabledOverlay({ disabled, disabledText }) { + const { t } = useTranslation(); return ( disabled && ( - {disabledText ? disabledText : "Disabled — Add .s2p file"} + {disabledText ?? t("settings.disabledS2p")} ) ); } export default function Settings({ settings, setSettings, usedF, chosenSparameter, chosenNoiseParameter }) { + const { t } = useTranslation(); const [QInt, setQInt] = useState(0); const [VSWRInt, setVSWRInt] = useState(0); const [gainInInt, setGainInInt] = useState(0); @@ -74,12 +77,12 @@ export default function Settings({ settings, setSettings, usedF, chosenSparamete return ( <> - Settings & Features + {t("settings.title")} setValue(e.target.value, "fRes", setSettings)} slotProps={{ input: { - endAdornment: pts, + endAdornment: {t("common.pts")}, }, }} /> @@ -149,7 +157,7 @@ export default function Settings({ settings, setSettings, usedF, chosenSparamete setQInt={setQInt} settings={settings} setSettings={setSettings} - title="Constant Q-factor circles" + title={t("settings.qCircles")} index="qCircles" disabled={false} /> @@ -159,7 +167,7 @@ export default function Settings({ settings, setSettings, usedF, chosenSparamete setQInt={setVSWRInt} settings={settings} setSettings={setSettings} - title="Constant VSWR circles" + title={t("settings.vswrCircles")} index="vswrCircles" disabled={false} /> @@ -169,11 +177,11 @@ export default function Settings({ settings, setSettings, usedF, chosenSparamete setQInt={setNFInt} settings={settings} setSettings={setSettings} - title="Constant Noise Figure circles" + title={t("settings.nfCircles")} index="nfCircles" unit="dB" disabled={!s2p || !chosenNoiseParameter} - disabledText="Disabled — Add .s2p with noise" + disabledText={t("settings.disabledNoise")} /> @@ -204,21 +214,22 @@ export default function Settings({ settings, setSettings, usedF, chosenSparamete } function CustomMarkersTable({ settings, setSettings }) { + const { t } = useTranslation(); const [polar, setPolar] = useState(false); const [zMarkersInt, setZMarkersInt] = useState([25, 25]); return ( - Impedance markers + {t("settings.markersTitle")} @@ -226,16 +237,16 @@ function CustomMarkersTable({ settings, setSettings }) { - Name + {t("common.name")} - {polar ? "Magnitude" : "Real"} + {polar ? t("settings.magnitude") : t("settings.real")} - {polar ? "Angle(°)" : "Imaginary"} + {polar ? t("settings.angleDeg") : t("settings.imaginary")} - Add + {t("common.add")} @@ -312,6 +323,7 @@ function CustomMarkersTable({ settings, setSettings }) { ); } function CustomQTable({ dB, QInt, maxValue, minValue, setQInt, settings, setSettings, title, index, unit, disabled, disabledText }) { + const { t } = useTranslation(); const [unitdB, setUnitdB] = useState(false); return ( - {title} {maxValue ? ` (max = ${maxValue.toPrecision(3)}dB)` : minValue ? ` (min = ${minValue.toPrecision(3)}dB)` : ""} + {title} + {maxValue ? t("settings.maxDb", { v: maxValue.toPrecision(3) }) : minValue ? t("settings.minDb", { v: minValue.toPrecision(3) }) : ""} {dB && ( )} @@ -339,10 +352,10 @@ function CustomQTable({ dB, QInt, maxValue, minValue, setQInt, settings, setSett - Value + {t("common.value")} - Add + {t("common.add")} diff --git a/src/Tutorials.jsx b/src/Tutorials.jsx index 15c9f23..be0f3cb 100644 --- a/src/Tutorials.jsx +++ b/src/Tutorials.jsx @@ -3,34 +3,36 @@ import AccordionSummary from "@mui/material/AccordionSummary"; import AccordionDetails from "@mui/material/AccordionDetails"; import Typography from "@mui/material/Typography"; import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; +import { useTranslation } from "react-i18next"; function Tutorials() { + const { t } = useTranslation(); return ( <> } aria-controls="panel1-content" id="panel1-header"> - Tutorials + {t("tutorials.title")} diff --git a/src/circuitComponents.js b/src/circuitComponents.js index 802b912..4b1e6e3 100644 --- a/src/circuitComponents.js +++ b/src/circuitComponents.js @@ -16,6 +16,7 @@ import s2p from "./assets/components/s2p.svg"; import { unitConverter } from "./commonFunctions.js"; import { parseTouchstoneFile } from "./sparam.js"; +import i18n from "./i18n.js"; const sparamDefaultS1P = { data: { @@ -405,7 +406,7 @@ export const circuitComponents = { }, fromURL: (u) => { if (u[4] == "tooLong") { - alert("The saved state included a 1000+ character s-parameter file. That was not saved so must be manually re-entered"); + alert(i18n.t("errors.sparamUrlTooLong")); return u[1] == "s1p" ? { ...sparamDefaultS1P } : { ...sparamDefaultS2P }; } var rawString = `# ${u[2]} S MA R ${u[3]}`; diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000..6978f12 --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,37 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import en from "./locales/en.json"; +import tr from "./locales/tr.json"; + +export const supportedLanguages = [ + { code: "en", label: "English" }, + { code: "tr", label: "Türkçe" }, +]; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources: { + en: { translation: en }, + tr: { translation: tr }, + }, + fallbackLng: "en", + supportedLngs: ["en", "tr"], + interpolation: { escapeValue: false }, + detection: { + order: ["localStorage", "navigator"], + caches: ["localStorage"], + lookupLocalStorage: "smith-chart-lang", + }, + }); + +if (typeof document !== "undefined") { + document.documentElement.lang = i18n.language; + i18n.on("languageChanged", (lng) => { + document.documentElement.lang = lng; + }); +} + +export default i18n; diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 0000000..74b3329 --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,248 @@ +{ + "common": { + "save": "Save", + "cancel": "Cancel", + "ok": "OK", + "add": "Add", + "delete": "Delete", + "name": "Name", + "value": "Value", + "pts": "pts", + "rectangular": "Rectangular", + "polar": "Polar", + "max": "max", + "min": "min", + "length": "length", + "re": "Re", + "im": "Im" + }, + "nav": { + "title": "ONLINE SMITH CHART TOOL", + "copyUrl": "Copy shareable URL", + "copyUrlAria": "Copy shareable URL to clipboard", + "reset": "Reset to initial state", + "resetAria": "Reset to initial state", + "urlCopied": "Shareable URL copied to clipboard! Your whole website state is in the URL", + "smithChart": "Smith Chart", + "circuitSolver": "Circuit Solver" + }, + "app": { + "intro": "Smith charts can help you design matching networks and obtain maximum power transfer between your source and load. Plot s-parameters.", + "urlLoadedSnackbar": "Some settings were loaded from the URL. Please click here to reset to the default state.", + "plotSparam": "Plot Raw S-Parameter Data", + "plotImpedanceS1p": "Plot Reflection Coefficient Looking Into DP1", + "plotImpedanceS2p": "Plot System Gain & Noise", + "commentsTitle": "Comment section below", + "supportLink": "Get professional support from Microwave Master here" + }, + "footer": { + "license": "© {{year}} Will Kelsey. This work is licensed under a Creative Commons Attribution 4.0 International License. You may not resell this tool", + "version": "v2.0" + }, + "language": { + "label": "Language" + }, + "settings": { + "title": "Settings & Features", + "zo": "Zo", + "frequency": "Frequency", + "fNotInSparam": "f not in s-param. Using {{f}}{{unit}}", + "frequencySpan": "Frequency Span ±", + "resolution": "Resolution", + "qCircles": "Constant Q-factor circles", + "vswrCircles": "Constant VSWR circles", + "nfCircles": "Constant Noise Figure circles", + "gainInCircles": "Input Gain Circles", + "gainOutCircles": "Output Gain Circles", + "disabledS2p": "Disabled — Add .s2p file", + "disabledNoise": "Disabled — Add .s2p with noise", + "markersTitle": "Impedance markers", + "magnitude": "Magnitude", + "real": "Real", + "angleDeg": "Angle(°)", + "imaginary": "Imaginary", + "vv": "V/V", + "db": "dB", + "maxDb": " (max = {{v}}dB)", + "minDb": " (min = {{v}}dB)" + }, + "results": { + "finalResults": "Final Results", + "impedanceOhm": "Impedance (Ω)", + "reflectionCoeff": "Reflection Coefficient", + "qFactor": "Q Factor", + "vswrTooltip": "Voltage Standing Wave Ratio", + "rawData": "Raw data", + "sMagPhase": "{{s}} magnitude and angle", + "zDp1": "Z looking into DP1", + "systemGain": "System Gain", + "noiseFigure": "Noise Figure", + "gainLegend": "Gain (dB)", + "nfLegend": "Noise Figure (dB)", + "assuming": "(assuming", + "maxS21": "Max S21: {{v}} dB at {{f}} {{unit}}", + "bw3db": "S21 3dB bandwidth: {{v}} {{unit}}", + "na": "N/A", + "frequencyAxis": "Frequency ({{unit}})", + "s11db": "|S11| (dB)", + "s11ang": "∠S11 (°)", + "s21db": "|S21| (dB)", + "gainAxis": "gain (dB)", + "tol": "tol {{i}}", + "tolPipe": "|tol{{i}}|", + "tolAng": "∠tol{{i}}" + }, + "graph": { + "downloadSvg": "Download SVG file", + "saveAria": "Save chart as SVG", + "stabilityCircles": "Stability circles", + "zDp1": "Z looking into DP1", + "zLabel": "Z", + "idealTooltip": "set all ESR and ESL to zero", + "showIdeal": "Show Ideal", + "graphSettings": "Graph Settings", + "dialogTitle": "Graph Settings", + "resistanceCircles": "Resistance Circles (Units of Zo)", + "reactanceCircles": "Reactance Circles (Units of Zo)", + "showAdmittance": "Show Admittance", + "hoverOutside": "Move cursor back inside the circle", + "frequency": "Frequency = {{v}} {{unit}}", + "impedance": "Impedance = {{z}} ({{polar}})", + "admittance": "Admittance = {{v}}", + "reflCoeff": "Refl-Coeff = {{v}} ({{polar}})", + "vswr": "VSWR = {{v}}", + "qFactorHover": "Q-Factor = {{v}}" + }, + "circuit": { + "components": { + "blackBox": "Black Box", + "loadTerm": "Load Termination", + "shortedCap": "Shorted Capacitor", + "seriesCap": "Series Capacitor", + "shortedInd": "Shorted Inductor", + "seriesInd": "Series Inductor", + "shortedRes": "Shorted Resistor", + "seriesRes": "Series Resistor", + "seriesRlc": "Parallel RLC", + "custom": "Custom Z(f)", + "transmissionLine": "Transmission Line", + "stub": "Stub", + "shortedStub": "Shorted Stub", + "transformer": "Transformer", + "sparam": "S-Parameter" + }, + "custom": { + "setZf": "Set Impedance vs Frequency", + "modalTitle": "Custom Impedance input box", + "desc1": "Enter rows of impedance vs increasing frequency", + "desc2": "Don't enter units or characters; use 2440e6 notation for 2440MHz", + "desc3": "comma separated: FREQUENCY, REAL, IMAGINARY", + "desc4": "whitespace separated: FREQUENCY REAL IMAGINARY", + "helperText": "If the textbox contains a comma it's assumed your data is comma separated, otherwise assumes whitespace separated. Each line must have 3 non-blank numberical values. The only accepted characters are 0-9, '-', '+', '.', e, E and ','. Frequency must be increasing", + "sampleHold": "Sample & Hold", + "linear": "Linear Interpolation", + "saveOrRemove": "Input error - remove component" + }, + "sparam": { + "gs0": ".{{type}} ~ GS0 = {{v}}dB", + "enterFile": "Enter S-param file", + "pasteTitle": "Paste .s1p or .s2p file contents below", + "s2pExample": "s2p example", + "s1pExample": "s1p example", + "chars": "{{n}} characters", + "urlLimit": ": 1K max for URL saving", + "noInputRemove": "No input - remove component?", + "inputErrorRemove": "Input error - remove component?", + "copyFile": "Copy in a file", + "pointsParsed": "{{n}} data points parsed succesfully", + "fileType": "File type: .{{type}}", + "freqUnit": "Frequency Unit: {{u}}", + "dataType": "Data Type: {{u}}", + "format": "Format: {{f}} ({{extra}})", + "rectangular": "Rectangular", + "polar": "Polar", + "zo": "Zo: {{v}}", + "tableIntro": "ABOVE TEXT FORMATTED INTO A TABLE", + "rowsFirst": "- First {{n}} rows of data: ", + "rowsAll": "- All rows of data: ", + "showLess": "Show less", + "showAll": "Show all", + "freqCol": "Frequency ({{u}})", + "noiseTitle": "Noise Data - note that noise frequencies not in s-param are discarded" + }, + "impedance": { + "zEquals": "Z =", + "editZTooltip": "Set impedance (auto-calculate component value)", + "enterZTitle": "Enter impedance", + "enterZHelper": "Enter numeric impedance (will be passed to the component calculator)" + }, + "transformer": { + "coupled": "Coupled-Inductor Model", + "ideal": "Ideal Transformer", + "l1": "L1", + "l2": "L2", + "couplingFactor": "Coupling Factor", + "turnsRatio": "Turns Ratio", + "ratioPrefix": "1 :" + }, + "labels": { + "inductance": "Inductance", + "capacitance": "Capacitance", + "resistance": "Resistance", + "length": "Length", + "zo": "Zo", + "eeff": "Eeff", + "eeffHelper": "Note - physical line length is changed", + "tolerance": "Tolerance", + "qFactorEquals": "Q Factor = {{v}}" + }, + "shortedStubDialog": "Shorted-stub arc starts at 0-ohms (length = 0), therefore will not be connected to the previous point. This may seem unintuitive but makes more sense once considering frequency-span and tolerance", + "hint": "Click components above to add them to the circuit below. Impedance is looking {{direction}}", + "towardsBlackBox": "towards the BLACK BOX", + "intoDp1": "into DP1", + "dp": "DP{{i}}" + }, + "tutorials": { + "title": "Tutorials", + "s1p": "Using .s1p files", + "s2p": "Using .s2p files", + "noise": "Designing for optimimum Noise Figure & Gain", + "stability": "Stability circles" + }, + "equations": { + "title": "Equations used by this site", + "item": "Item", + "equation": "Equation", + "notes": "Notes" + }, + "release": { + "title": "Release Notes", + "version": "Version", + "date": "Date", + "notes": "Notes", + "v21": ["Added s-parameter component", "Added gain circles for turning s2p gain", "Added a couple of s-parameter tutorials"], + "v20a": "Site moved from will-kelsey.com/smith_chart/ to onlinesmithchart.com", + "v20b": "Whole site re-written. Over 6 years many features were added to the old site which left the code very messy. Some users were looking at the code to verify implementations, which was tough. Now, the code is much more organised and optimized. In the future I hope the community can add features and improvements to the codebase.", + "v20c": "Moved from vanilla JS to React + MUI. This allows: running lint, more maintainable code, smaller file size, many micro-benefits from joining the mainstream", + "v20d": "As well as a re-write, the following new features are added:", + "v20features": [ + "Smith chart is interactive - hover over the chart. This makes it possible to see Z when there are N curves (tol, fspan)", + "Components have sliders - quickly see whether to increase or decrease component values", + "Add Noise Figure circles", + "Add transformer component", + "Save whole state in the URL" + ], + "v10italic": "Adding a brief history of the site", + "v10a": "I needed to match a Maxim 2.4GHz bluetooth amplifier (with a 25+25j output impedace) to a 50ohm chip antenna. The only software I could find was from Fritz Dellsperger however on Windows 11 the GUI became unusable.", + "v10b": "The first version of this site was extremely simple to support my basic needs; a black box, capacitor, inductor and smith chart diagram. We successfully used the tool to chose our component values.", + "v10c": "The community has made hundreds of requests over the years and many features have been added", + "v10d": "All features have been verified against Fritz's software, YouTube videos, allaboutcircuits.com, etc. Of coursethere were some mistakes, most of these have been identified and fixed during the 100's of comments" + }, + "meta": { + "pageTitle": "Smith Chart Online", + "pageDescription": "Free Online Smith Chart Tool" + }, + "errors": { + "sparamUrlTooLong": "The saved state included a 1000+ character s-parameter file. That was not saved so must be manually re-entered" + } +} diff --git a/src/locales/tr.json b/src/locales/tr.json new file mode 100644 index 0000000..091a4e1 --- /dev/null +++ b/src/locales/tr.json @@ -0,0 +1,248 @@ +{ + "common": { + "save": "Kaydet", + "cancel": "İptal", + "ok": "Tamam", + "add": "Ekle", + "delete": "Sil", + "name": "Ad", + "value": "Değer", + "pts": "nokta", + "rectangular": "Dikgen", + "polar": "Kutupsal", + "max": "maks", + "min": "min", + "length": "uzunluk", + "re": "Re", + "im": "İm" + }, + "nav": { + "title": "ÇEVRİMİÇİ SMITH CHART ARACI", + "copyUrl": "Paylaşılabilir URL'yi kopyala", + "copyUrlAria": "Paylaşılabilir URL'yi panoya kopyala", + "reset": "Başlangıç durumuna sıfırla", + "resetAria": "Başlangıç durumuna sıfırla", + "urlCopied": "Paylaşılabilir URL panoya kopyalandı! Tüm site durumunuz URL içinde", + "smithChart": "Smith Chart", + "circuitSolver": "Devre Çözücü" + }, + "app": { + "intro": "Smith chart’lar eşleştirme ağları tasarlamanıza ve kaynak ile yük arasında en yüksek güç aktarımını sağlamanıza yardımcı olur. S-parametreleri çizin.", + "urlLoadedSnackbar": "Bazı ayarlar URL’den yüklendi. Varsayılan duruma dönmek için buraya tıklayın.", + "plotSparam": "Ham S-parametre verisini çiz", + "plotImpedanceS1p": "DP1’e bakan yansıma katsayısını çiz", + "plotImpedanceS2p": "Sistem kazancı ve gürültüyü çiz", + "commentsTitle": "Aşağıda yorum bölümü", + "supportLink": "Microwave Master’dan profesyonel destek alın" + }, + "footer": { + "license": "© {{year}} Will Kelsey. Bu çalışma Creative Commons Atıf 4.0 Uluslararası Lisansı altındadır. Bu aracı yeniden satamazsınız", + "version": "sürüm 2.0" + }, + "language": { + "label": "Dil" + }, + "settings": { + "title": "Ayarlar ve özellikler", + "zo": "Zo", + "frequency": "Frekans", + "fNotInSparam": "f, s-param içinde yok. Kullanılan: {{f}}{{unit}}", + "frequencySpan": "Frekans aralığı ±", + "resolution": "Çözünürlük", + "qCircles": "Sabit Q çemberleri", + "vswrCircles": "Sabit VSWR çemberleri", + "nfCircles": "Sabit gürültü katsayısı çemberleri", + "gainInCircles": "Giriş kazanç çemberleri", + "gainOutCircles": "Çıkış kazanç çemberleri", + "disabledS2p": "Kapalı — .s2p dosyası ekleyin", + "disabledNoise": "Kapalı — gürültülü .s2p ekleyin", + "markersTitle": "Empedans işaretçileri", + "magnitude": "Genlik", + "real": "Gerçel", + "angleDeg": "Açı (°)", + "imaginary": "Sanal", + "vv": "V/V", + "db": "dB", + "maxDb": " (maks = {{v}} dB)", + "minDb": " (min = {{v}} dB)" + }, + "results": { + "finalResults": "Sonuçlar", + "impedanceOhm": "Empedans (Ω)", + "reflectionCoeff": "Yansıma katsayısı", + "qFactor": "Q faktörü", + "vswrTooltip": "Gerilim duran dalga oranı (VSWR)", + "rawData": "Ham veri", + "sMagPhase": "{{s}} genlik ve faz", + "zDp1": "DP1’e bakan Z", + "systemGain": "Sistem kazancı", + "noiseFigure": "Gürültü katsayısı", + "gainLegend": "Kazanç (dB)", + "nfLegend": "Gürültü katsayısı (dB)", + "assuming": "(varsayım:", + "maxS21": "Maks S21: {{v}} dB, {{f}} {{unit}}", + "bw3db": "S21 3 dB bant genişliği: {{v}} {{unit}}", + "na": "Yok", + "frequencyAxis": "Frekans ({{unit}})", + "s11db": "|S11| (dB)", + "s11ang": "∠S11 (°)", + "s21db": "|S21| (dB)", + "gainAxis": "kazanç (dB)", + "tol": "tol {{i}}", + "tolPipe": "|tol{{i}}|", + "tolAng": "∠tol{{i}}" + }, + "graph": { + "downloadSvg": "SVG dosyasını indir", + "saveAria": "Grafiği SVG olarak kaydet", + "stabilityCircles": "Kararlılık çemberleri", + "zDp1": "DP1’e bakan Z", + "zLabel": "Z", + "idealTooltip": "Tüm ESR ve ESL değerlerini sıfırla", + "showIdeal": "İdeal göster", + "graphSettings": "Grafik ayarları", + "dialogTitle": "Grafik ayarları", + "resistanceCircles": "Direnç çemberleri (Zo biriminde)", + "reactanceCircles": "Reaktans çemberleri (Zo biriminde)", + "showAdmittance": "Admitansı göster", + "hoverOutside": "İmleci çemberin içine geri getirin", + "frequency": "Frekans = {{v}} {{unit}}", + "impedance": "Empedans = {{z}} ({{polar}})", + "admittance": "Admitans = {{v}}", + "reflCoeff": "Yansıma kats. = {{v}} ({{polar}})", + "vswr": "VSWR = {{v}}", + "qFactorHover": "Q faktörü = {{v}}" + }, + "circuit": { + "components": { + "blackBox": "Kara kutu", + "loadTerm": "Yük sonlandırması", + "shortedCap": "Kısa devre kapasitör", + "seriesCap": "Seri kapasitör", + "shortedInd": "Kısa devre endüktör", + "seriesInd": "Seri endüktör", + "shortedRes": "Kısa devre direnç", + "seriesRes": "Seri direnç", + "seriesRlc": "Paralel RLC", + "custom": "Özel Z(f)", + "transmissionLine": "İletim hattı", + "stub": "Stub", + "shortedStub": "Kısa devre stub", + "transformer": "Transformatör", + "sparam": "S-parametre" + }, + "custom": { + "setZf": "Frekansa göre empedansı ayarla", + "modalTitle": "Özel empedans girişi", + "desc1": "Artan frekansa göre empedans satırlarını girin", + "desc2": "Birim veya ek karakter girmeyin; 2440 MHz için 2440e6 gösterimini kullanın", + "desc3": "virgülle ayrılmış: FREKANS, GERÇEL, SANAL", + "desc4": "boşlukla ayrılmış: FREKANS GERÇEL SANAL", + "helperText": "Metinde virgül varsa veri virgülle ayrılmış kabul edilir, yoksa boşlukla ayrılmış kabul edilir. Her satırda 3 boş olmayan sayısal değer olmalıdır. Yalnızca 0-9, '-', '+', '.', e, E ve ',' kabul edilir. Frekans artan sırada olmalıdır", + "sampleHold": "Örnekle ve tut", + "linear": "Doğrusal enterpolasyon", + "saveOrRemove": "Giriş hatası — bileşeni kaldır" + }, + "sparam": { + "gs0": ".{{type}} ~ GS0 = {{v}} dB", + "enterFile": "S-param dosyası gir", + "pasteTitle": "Aşağıya .s1p veya .s2p dosya içeriğini yapıştırın", + "s2pExample": "s2p örneği", + "s1pExample": "s1p örneği", + "chars": "{{n}} karakter", + "urlLimit": ": URL kaydı için en fazla 1K", + "noInputRemove": "Giriş yok — bileşen kaldırılsın mı?", + "inputErrorRemove": "Giriş hatası — bileşen kaldırılsın mı?", + "copyFile": "Bir dosya yapıştırın", + "pointsParsed": "{{n}} veri noktası başarıyla ayrıştırıldı", + "fileType": "Dosya türü: .{{type}}", + "freqUnit": "Frekans birimi: {{u}}", + "dataType": "Veri türü: {{u}}", + "format": "Biçim: {{f}} ({{extra}})", + "rectangular": "Dikgen", + "polar": "Kutupsal", + "zo": "Zo: {{v}}", + "tableIntro": "YUKARIDAKİ METİN TABLO OLARAK", + "rowsFirst": "- İlk {{n}} veri satırı: ", + "rowsAll": "- Tüm veri satırları: ", + "showLess": "Daha az göster", + "showAll": "Tümünü göster", + "freqCol": "Frekans ({{u}})", + "noiseTitle": "Gürültü verisi — s-param frekanslarında olmayan gürültü noktaları atılır" + }, + "impedance": { + "zEquals": "Z =", + "editZTooltip": "Empedansı ayarla (bileşen değerini otomatik hesapla)", + "enterZTitle": "Empedans girin", + "enterZHelper": "Sayısal empedans girin (bileşen hesaplayıcısına iletilir)" + }, + "transformer": { + "coupled": "Bağlı endüktör modeli", + "ideal": "İdeal transformatör", + "l1": "L1", + "l2": "L2", + "couplingFactor": "Bağlanma katsayısı", + "turnsRatio": "Sarım oranı", + "ratioPrefix": "1 :" + }, + "labels": { + "inductance": "Endüktans", + "capacitance": "Kapasitans", + "resistance": "Direnç", + "length": "Uzunluk", + "zo": "Zo", + "eeff": "Eeff", + "eeffHelper": "Not — fiziksel hat uzunluğu değişir", + "tolerance": "Tolerans", + "qFactorEquals": "Q faktörü = {{v}}" + }, + "shortedStubDialog": "Kısa devre stub yayı 0 Ω’da (uzunluk = 0) başlar, bu yüzden önceki noktaya bağlı görünmez. İlk bakışta karşı sezgine gelebilir; frekans aralığı ve tolerans düşünüldüğünde daha anlaşılır olur", + "hint": "Aşağıdaki devreye eklemek için yukarıdaki bileşenlere tıklayın. Empedans yönü: {{direction}}", + "towardsBlackBox": "KARA KUTU’ya doğru", + "intoDp1": "DP1 içine", + "dp": "DP{{i}}" + }, + "tutorials": { + "title": "Öğreticiler", + "s1p": ".s1p dosyaları kullanımı", + "s2p": ".s2p dosyaları kullanımı", + "noise": "En uygun gürültü katsayısı ve kazanç için tasarım", + "stability": "Kararlılık çemberleri" + }, + "equations": { + "title": "Bu sitede kullanılan denklemler", + "item": "Öğe", + "equation": "Denklem", + "notes": "Notlar" + }, + "release": { + "title": "Sürüm notları", + "version": "Sürüm", + "date": "Tarih", + "notes": "Notlar", + "v21": ["S-parametre bileşeni eklendi", "s2p kazancı için kazanç çemberleri eklendi", "Birkaç s-parametre öğreticisi eklendi"], + "v20a": "Site will-kelsey.com/smith_chart/ adresinden onlinesmithchart.com adresine taşındı", + "v20b": "Tüm site yeniden yazıldı. 6 yıldan uzun sürede eski siteye birçok özellik eklendiği için kod çok dağınıktı. Bazı kullanıcılar doğrulama için koda bakıyordu; bu zordu. Artık kod daha düzenli ve optimize. İleride topluluğun depoya özellik ve iyileştirme eklemesini umuyorum.", + "v20c": "Vanilya JS’ten React + MUI’ye geçildi. Bu; lint çalıştırma, daha sürdürülebilir kod, daha küçük paket ve ana akıma uyumdan gelen birçok küçük avantaj sağlar", + "v20d": "Yeniden yazımın yanı sıra şu yeni özellikler eklendi:", + "v20features": [ + "Smith chart etkileşimli — grafiğin üzerine gelin. N eğri (tol, fspan) varken Z’yi görmeyi sağlar", + "Bileşenlerde kaydırıcılar — değerleri artırıp azaltmayı hızlıca görün", + "Gürültü katsayısı çemberleri", + "Transformatör bileşeni", + "Tüm durumu URL’de kaydetme" + ], + "v10italic": "Sitenin kısa tarihçesi", + "v10a": "Maxim 2,4 GHz Bluetooth yükseltecini (25+25j çıkış empedansı) 50 Ω çip antenine eşleştirmem gerekiyordu. Bulabildiğim tek yazılım Fritz Dellsperger’indi; Windows 11’de arayüz kullanılamaz hale gelmişti.", + "v10b": "Sitenin ilk sürümü temel ihtiyaçlarım için çok basitti; kara kutu, kapasitör, endüktör ve Smith chart. Aracı bileşen değerlerini seçmek için başarıyla kullandık.", + "v10c": "Yıllar içinde topluluk yüzlerce istekte bulundu ve birçok özellik eklendi", + "v10d": "Özellikler Fritz yazılımı, YouTube videoları, allaboutcircuits.com vb. ile doğrulandı. Elbette hatalar da oldu; çoğu yüzlerce yorumda tespit edilip düzeltildi" + }, + "meta": { + "pageTitle": "Smith Chart Çevrimiçi", + "pageDescription": "Ücretsiz çevrimiçi Smith Chart aracı" + }, + "errors": { + "sparamUrlTooLong": "Kaydedilen durum 1000+ karakterlik bir s-parametre dosyası içeriyordu. Bu URL’ye kaydedilmedi; el ile yeniden girmeniz gerekir" + } +} diff --git a/src/main.jsx b/src/main.jsx index fdce256..80214c5 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,5 +1,6 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import "./i18n.js"; import "./index.css"; import App from "./App.jsx";