add internationalization base configuration and internationalize App.tsx
This commit is contained in:
parent
21f553a0d8
commit
658e0a497f
@ -36,10 +36,14 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"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-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
|
||||
10
frontend/public/locales/en/translation.json
Normal file
10
frontend/public/locales/en/translation.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
10
frontend/public/locales/zh/translation.json
Normal file
10
frontend/public/locales/zh/translation.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "截图转代码",
|
||||
"errors": {
|
||||
"emptyInstruction": "请为AI提供一些关于更新内容的指示。",
|
||||
"noCurrentVersion": "未设置当前版本。请联系支持或在Github上开issue。",
|
||||
"invalidHistory": "版本历史无效。这不应该发生。请联系支持或在Github上开issue。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -23,8 +23,11 @@ import DeprecationMessage from "./components/messages/DeprecationMessage";
|
||||
import { GenerationSettings } from "./components/settings/GenerationSettings";
|
||||
import StartPane from "./components/start-pane/StartPane";
|
||||
import { takeScreenshot } from "./lib/takeScreenshot";
|
||||
import { useTranslation } from 'react-i18next'; // 导入 useTranslation hook
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation(); // 初始化 useTranslation hook
|
||||
|
||||
const {
|
||||
// Inputs
|
||||
inputMode,
|
||||
@ -118,16 +121,14 @@ function App() {
|
||||
|
||||
const regenerate = () => {
|
||||
if (currentVersion === null) {
|
||||
toast.error(
|
||||
"No current version set. Please open a Github issue as this shouldn't happen."
|
||||
);
|
||||
toast.error(t('app.errors.noCurrentVersion'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the previous command
|
||||
const previousCommand = appHistory[currentVersion];
|
||||
if (previousCommand.type !== "ai_create") {
|
||||
toast.error("Only the first version can be regenerated.");
|
||||
toast.error(t('app.errors.regenerateFirstVersion'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -189,9 +190,7 @@ function App() {
|
||||
setAppHistory((prev) => {
|
||||
// Validate parent version
|
||||
if (parentVersion === null) {
|
||||
toast.error(
|
||||
"No parent version set. Contact support or open a Github issue."
|
||||
);
|
||||
toast.error(t('app.errors.noCurrentVersion'));
|
||||
return prev;
|
||||
}
|
||||
|
||||
@ -254,14 +253,12 @@ function App() {
|
||||
selectedElement?: HTMLElement
|
||||
) {
|
||||
if (updateInstruction.trim() === "") {
|
||||
toast.error("Please include some instructions for AI on what to update.");
|
||||
toast.error(t('app.errors.emptyInstruction'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVersion === null) {
|
||||
toast.error(
|
||||
"No current version set. Contact support or open a Github issue."
|
||||
);
|
||||
toast.error(t('app.errors.noCurrentVersion'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -269,9 +266,7 @@ function App() {
|
||||
try {
|
||||
historyTree = extractHistoryTree(appHistory, currentVersion);
|
||||
} catch {
|
||||
toast.error(
|
||||
"Version history is invalid. This shouldn't happen. Please contact support or open a Github issue."
|
||||
);
|
||||
toast.error(t('app.errors.invalidHistory'));
|
||||
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">
|
||||
{/* Header with access to settings */}
|
||||
<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} />
|
||||
</div>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
@ -22,6 +22,7 @@ import {
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "../ui/accordion";
|
||||
import LanguageSelector from "../languageSelector/LanguageSelector";
|
||||
|
||||
interface Props {
|
||||
settings: Settings;
|
||||
@ -131,7 +132,10 @@ function SettingsDialog({ settings, setSettings }: Props) {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="language-select">Language</Label>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Screenshot by URL Config</AccordionTrigger>
|
||||
|
||||
18
frontend/src/i18n.ts
Normal file
18
frontend/src/i18n.ts
Normal 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;
|
||||
@ -5,6 +5,7 @@ import "./index.css";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import EvalsPage from "./components/evals/EvalsPage.tsx";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import './i18n';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
|
||||
@ -420,6 +420,13 @@
|
||||
dependencies:
|
||||
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":
|
||||
version "7.22.15"
|
||||
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"
|
||||
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:
|
||||
version "7.0.3"
|
||||
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"
|
||||
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:
|
||||
version "1.4.1"
|
||||
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"
|
||||
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:
|
||||
version "1.2.1"
|
||||
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"
|
||||
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:
|
||||
version "5.4.2"
|
||||
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:
|
||||
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:
|
||||
version "4.12.0"
|
||||
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"
|
||||
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:
|
||||
version "1.0.3"
|
||||
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"
|
||||
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:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz"
|
||||
@ -5825,6 +5892,11 @@ walker@^1.0.8:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.0.4"
|
||||
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"
|
||||
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:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user