add internationalization base configuration and internationalize App.tsx

This commit is contained in:
wsh 2024-08-17 19:20:42 +08:00
parent 21f553a0d8
commit 658e0a497f
10 changed files with 202 additions and 17 deletions

View File

@ -36,10 +36,14 @@
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"i18next": "^23.13.0",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-i18next": "^15.0.1",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-router-dom": "^6.20.1", "react-router-dom": "^6.20.1",
"tailwind-merge": "^2.0.0", "tailwind-merge": "^2.0.0",

View File

@ -0,0 +1,10 @@
{
"app": {
"title": "Screenshot to Code",
"errors": {
"emptyInstruction": "Please include some instructions for AI on what to update.",
"noCurrentVersion": "No current version set. Contact support or open a Github issue.",
"invalidHistory": "Version history is invalid. This shouldn't happen. Please contact support or open a Github issue."
}
}
}

View File

@ -0,0 +1,10 @@
{
"app": {
"title": "截图转代码",
"errors": {
"emptyInstruction": "请为AI提供一些关于更新内容的指示。",
"noCurrentVersion": "未设置当前版本。请联系支持或在Github上开issue。",
"invalidHistory": "版本历史无效。这不应该发生。请联系支持或在Github上开issue。"
}
}
}

View File

@ -23,8 +23,11 @@ import DeprecationMessage from "./components/messages/DeprecationMessage";
import { GenerationSettings } from "./components/settings/GenerationSettings"; import { GenerationSettings } from "./components/settings/GenerationSettings";
import StartPane from "./components/start-pane/StartPane"; import StartPane from "./components/start-pane/StartPane";
import { takeScreenshot } from "./lib/takeScreenshot"; import { takeScreenshot } from "./lib/takeScreenshot";
import { useTranslation } from 'react-i18next'; // 导入 useTranslation hook
function App() { function App() {
const { t } = useTranslation(); // 初始化 useTranslation hook
const { const {
// Inputs // Inputs
inputMode, inputMode,
@ -118,16 +121,14 @@ function App() {
const regenerate = () => { const regenerate = () => {
if (currentVersion === null) { if (currentVersion === null) {
toast.error( toast.error(t('app.errors.noCurrentVersion'));
"No current version set. Please open a Github issue as this shouldn't happen."
);
return; return;
} }
// Retrieve the previous command // Retrieve the previous command
const previousCommand = appHistory[currentVersion]; const previousCommand = appHistory[currentVersion];
if (previousCommand.type !== "ai_create") { if (previousCommand.type !== "ai_create") {
toast.error("Only the first version can be regenerated."); toast.error(t('app.errors.regenerateFirstVersion'));
return; return;
} }
@ -189,9 +190,7 @@ function App() {
setAppHistory((prev) => { setAppHistory((prev) => {
// Validate parent version // Validate parent version
if (parentVersion === null) { if (parentVersion === null) {
toast.error( toast.error(t('app.errors.noCurrentVersion'));
"No parent version set. Contact support or open a Github issue."
);
return prev; return prev;
} }
@ -254,14 +253,12 @@ function App() {
selectedElement?: HTMLElement selectedElement?: HTMLElement
) { ) {
if (updateInstruction.trim() === "") { if (updateInstruction.trim() === "") {
toast.error("Please include some instructions for AI on what to update."); toast.error(t('app.errors.emptyInstruction'));
return; return;
} }
if (currentVersion === null) { if (currentVersion === null) {
toast.error( toast.error(t('app.errors.noCurrentVersion'));
"No current version set. Contact support or open a Github issue."
);
return; return;
} }
@ -269,9 +266,7 @@ function App() {
try { try {
historyTree = extractHistoryTree(appHistory, currentVersion); historyTree = extractHistoryTree(appHistory, currentVersion);
} catch { } catch {
toast.error( toast.error(t('app.errors.invalidHistory'));
"Version history is invalid. This shouldn't happen. Please contact support or open a Github issue."
);
return; return;
} }
@ -365,7 +360,7 @@ function App() {
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white"> <div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white">
{/* Header with access to settings */} {/* Header with access to settings */}
<div className="flex items-center justify-between mt-10 mb-2"> <div className="flex items-center justify-between mt-10 mb-2">
<h1 className="text-2xl ">Screenshot to Code</h1> <h1 className="text-2xl ">{t('app.title')}</h1>
<SettingsDialog settings={settings} setSettings={setSettings} /> <SettingsDialog settings={settings} setSettings={setSettings} />
</div> </div>

View File

@ -0,0 +1,20 @@
.language-selector-container {
display: inline-block;
}
.language-selector {
color: white;
background-color: #333;
padding: 8px 32px 8px 12px;
font-size: 14px;
border: 1px solid #444;
border-radius: 4px;
cursor: pointer;
outline: none;
transition: all 0.3s ease;
}
.language-selector:hover, .language-selector:focus {
background-color: #444;
border-color: #555;
}

View File

@ -0,0 +1,43 @@
import React, { useState, useEffect } from 'react';
import i18n from '../../i18n';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import './LanguageSelector.css';
type Languages = 'en' | 'zh';
const LanguageSelector: React.FC = () => {
const getBrowserLanguage = () => {
const browserLang = navigator.language.split('-')[0];
return (['en', 'zh'].includes(browserLang) ? browserLang : 'en') as Languages;
};
const [selectedLang, setSelectedLang] = useState<Languages>(i18n.language as Languages);
useEffect(() => {
const defaultLang = getBrowserLanguage();
if (defaultLang !== i18n.language) {
i18n.changeLanguage(defaultLang);
setSelectedLang(defaultLang);
}
}, []);
const handleLanguageChange = (value: string) => {
const nextLang = value as Languages;
i18n.changeLanguage(nextLang);
setSelectedLang(nextLang);
};
return (
<Select onValueChange={handleLanguageChange} value={selectedLang}>
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="Language" />
</SelectTrigger>
<SelectContent>
<SelectItem value="en">English</SelectItem>
<SelectItem value="zh"></SelectItem>
</SelectContent>
</Select>
);
};
export default LanguageSelector;

View File

@ -22,6 +22,7 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
} from "../ui/accordion"; } from "../ui/accordion";
import LanguageSelector from "../languageSelector/LanguageSelector";
interface Props { interface Props {
settings: Settings; settings: Settings;
@ -131,7 +132,10 @@ function SettingsDialog({ settings, setSettings }: Props) {
} }
/> />
</div> </div>
<div className="flex items-center justify-between">
<Label htmlFor="language-select">Language</Label>
<LanguageSelector />
</div>
<Accordion type="single" collapsible className="w-full"> <Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1"> <AccordionItem value="item-1">
<AccordionTrigger>Screenshot by URL Config</AccordionTrigger> <AccordionTrigger>Screenshot by URL Config</AccordionTrigger>

18
frontend/src/i18n.ts Normal file
View File

@ -0,0 +1,18 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import HttpBackend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(HttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false
}
});
export default i18n;

View File

@ -5,6 +5,7 @@ import "./index.css";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import EvalsPage from "./components/evals/EvalsPage.tsx"; import EvalsPage from "./components/evals/EvalsPage.tsx";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import './i18n';
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>

View File

@ -420,6 +420,13 @@
dependencies: dependencies:
regenerator-runtime "^0.14.0" regenerator-runtime "^0.14.0"
"@babel/runtime@^7.23.2", "@babel/runtime@^7.24.8":
version "7.25.0"
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb"
integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.15": "@babel/template@^7.22.15":
version "7.22.15" version "7.22.15"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz" resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz"
@ -2735,6 +2742,13 @@ crelt@^1.0.5:
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
cross-fetch@4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983"
integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==
dependencies:
node-fetch "^2.6.12"
cross-spawn@^7.0.2, cross-spawn@^7.0.3: cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
@ -3563,6 +3577,13 @@ html-minifier-terser@^6.1.0:
relateurl "^0.2.7" relateurl "^0.2.7"
terser "^5.10.0" terser "^5.10.0"
html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"
html2canvas@^1.4.1: html2canvas@^1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543" resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
@ -3597,6 +3618,27 @@ human-signals@^5.0.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
i18next-browser-languagedetector@^8.0.0:
version "8.0.0"
resolved "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz#b6fdd9b43af67c47f2c26c9ba27710a1eaf31e2f"
integrity sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==
dependencies:
"@babel/runtime" "^7.23.2"
i18next-http-backend@^2.5.2:
version "2.5.2"
resolved "https://registry.npmmirror.com/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz#3d846cc239987fe7700d1cf0f17975807bfd25d3"
integrity sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==
dependencies:
cross-fetch "4.0.0"
i18next@^23.13.0:
version "23.13.0"
resolved "https://registry.npmmirror.com/i18next/-/i18next-23.13.0.tgz#d3cba6c5611b9826ff988f97a4929638a91875f6"
integrity sha512-B+g0/KTKmN3+NeMKPljQxdrih6Q6lyDF5O2e/Ofd0JQsTLojJD/BSTTN04iw6OVc0yBiHeypu5hoBNV6ag44Zw==
dependencies:
"@babel/runtime" "^7.23.2"
ieee754@^1.1.13, ieee754@^1.2.1: ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@ -4459,6 +4501,13 @@ no-case@^3.0.4:
lower-case "^2.0.2" lower-case "^2.0.2"
tslib "^2.0.3" tslib "^2.0.3"
node-fetch@^2.6.12:
version "2.7.0"
resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
node-html-parser@^5.3.3: node-html-parser@^5.3.3:
version "5.4.2" version "5.4.2"
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.4.2.tgz#93e004038c17af80226c942336990a0eaed8136a" resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.4.2.tgz#93e004038c17af80226c942336990a0eaed8136a"
@ -4948,6 +4997,14 @@ react-hot-toast@^2.4.1:
dependencies: dependencies:
goober "^2.1.10" goober "^2.1.10"
react-i18next@^15.0.1:
version "15.0.1"
resolved "https://registry.npmmirror.com/react-i18next/-/react-i18next-15.0.1.tgz#fc662d93829ecb39683fe2757a47ebfbc5c912a0"
integrity sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w==
dependencies:
"@babel/runtime" "^7.24.8"
html-parse-stringify "^3.0.1"
react-icons@^4.12.0: react-icons@^4.12.0:
version "4.12.0" version "4.12.0"
resolved "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz" resolved "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz"
@ -5528,6 +5585,11 @@ toggle-selection@^1.0.6:
resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
ts-api-utils@^1.0.1: ts-api-utils@^1.0.1:
version "1.0.3" version "1.0.3"
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz" resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz"
@ -5769,6 +5831,11 @@ vitest@^1.0.1:
vite-node "1.0.1" vite-node "1.0.1"
why-is-node-running "^2.2.2" why-is-node-running "^2.2.2"
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
vscode-jsonrpc@6.0.0: vscode-jsonrpc@6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz" resolved "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz"
@ -5825,6 +5892,11 @@ walker@^1.0.8:
dependencies: dependencies:
makeerror "1.0.12" makeerror "1.0.12"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webm-duration-fix@^1.0.4: webm-duration-fix@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/webm-duration-fix/-/webm-duration-fix-1.0.4.tgz#fef235cb3d3ed3363507f705a7577dbb9fdedae6" resolved "https://registry.yarnpkg.com/webm-duration-fix/-/webm-duration-fix-1.0.4.tgz#fef235cb3d3ed3363507f705a7577dbb9fdedae6"
@ -5835,6 +5907,14 @@ webm-duration-fix@^1.0.4:
events "^3.3.0" events "^3.3.0"
int64-buffer "^1.0.1" int64-buffer "^1.0.1"
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which@^2.0.1: which@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"