From 658e0a497fed9dfdb440897f930f798553cc01ba Mon Sep 17 00:00:00 2001 From: wsh Date: Sat, 17 Aug 2024 19:20:42 +0800 Subject: [PATCH 1/4] add internationalization base configuration and internationalize App.tsx --- frontend/package.json | 4 + frontend/public/locales/en/translation.json | 10 +++ frontend/public/locales/zh/translation.json | 10 +++ frontend/src/App.tsx | 27 +++---- .../languageSelector/LanguageSelector.css | 20 +++++ .../languageSelector/LanguageSelector.tsx | 43 ++++++++++ .../components/settings/SettingsDialog.tsx | 6 +- frontend/src/i18n.ts | 18 +++++ frontend/src/main.tsx | 1 + frontend/yarn.lock | 80 +++++++++++++++++++ 10 files changed, 202 insertions(+), 17 deletions(-) create mode 100644 frontend/public/locales/en/translation.json create mode 100644 frontend/public/locales/zh/translation.json create mode 100644 frontend/src/components/languageSelector/LanguageSelector.css create mode 100644 frontend/src/components/languageSelector/LanguageSelector.tsx create mode 100644 frontend/src/i18n.ts diff --git a/frontend/package.json b/frontend/package.json index 4652dc70..7b14477d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json new file mode 100644 index 00000000..fd751be4 --- /dev/null +++ b/frontend/public/locales/en/translation.json @@ -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." + } + } +} \ No newline at end of file diff --git a/frontend/public/locales/zh/translation.json b/frontend/public/locales/zh/translation.json new file mode 100644 index 00000000..93ac6214 --- /dev/null +++ b/frontend/public/locales/zh/translation.json @@ -0,0 +1,10 @@ +{ + "app": { + "title": "截图转代码", + "errors": { + "emptyInstruction": "请为AI提供一些关于更新内容的指示。", + "noCurrentVersion": "未设置当前版本。请联系支持或在Github上开issue。", + "invalidHistory": "版本历史无效。这不应该发生。请联系支持或在Github上开issue。" + } + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9c20df33..be98d1ab 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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() {
{/* Header with access to settings */}
-

Screenshot to Code

+

{t('app.title')}

@@ -414,4 +409,4 @@ function App() { ); } -export default App; +export default App; \ No newline at end of file diff --git a/frontend/src/components/languageSelector/LanguageSelector.css b/frontend/src/components/languageSelector/LanguageSelector.css new file mode 100644 index 00000000..282b9a9e --- /dev/null +++ b/frontend/src/components/languageSelector/LanguageSelector.css @@ -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; +} diff --git a/frontend/src/components/languageSelector/LanguageSelector.tsx b/frontend/src/components/languageSelector/LanguageSelector.tsx new file mode 100644 index 00000000..0655026d --- /dev/null +++ b/frontend/src/components/languageSelector/LanguageSelector.tsx @@ -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(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 ( + + ); +}; + +export default LanguageSelector; \ No newline at end of file diff --git a/frontend/src/components/settings/SettingsDialog.tsx b/frontend/src/components/settings/SettingsDialog.tsx index 4283ad46..9bbc59a2 100644 --- a/frontend/src/components/settings/SettingsDialog.tsx +++ b/frontend/src/components/settings/SettingsDialog.tsx @@ -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) { } />
- +
+ + +
Screenshot by URL Config diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts new file mode 100644 index 00000000..28e90c98 --- /dev/null +++ b/frontend/src/i18n.ts @@ -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; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c4224f46..8fc6816c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -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( diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e1254e8f..361edc76 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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" From d2ddb2d261f5859de0fe99628b06a5b72d227489 Mon Sep 17 00:00:00 2001 From: wsh Date: Sat, 17 Aug 2024 22:56:10 +0800 Subject: [PATCH 2/4] Add international content and adapt internationally --- frontend/public/locales/de/translation.json | 145 ++++++++++++++++++ frontend/public/locales/en/translation.json | 135 ++++++++++++++++ frontend/public/locales/fr/translation.json | 145 ++++++++++++++++++ frontend/public/locales/ja/translation.json | 145 ++++++++++++++++++ frontend/public/locales/ko/translation.json | 145 ++++++++++++++++++ frontend/public/locales/ru/translation.json | 145 ++++++++++++++++++ frontend/public/locales/zh/translation.json | 135 ++++++++++++++++ frontend/src/components/ImageUpload.tsx | 12 +- frontend/src/components/ImportCodeSection.tsx | 17 +- .../src/components/TermsOfServiceDialog.tsx | 22 +-- frontend/src/components/UrlInputSection.tsx | 21 ++- .../src/components/history/HistoryDisplay.tsx | 12 +- frontend/src/components/history/utils.ts | 10 +- .../messages/DeprecationMessage.tsx | 7 +- .../components/messages/OnboardingNote.tsx | 14 +- .../src/components/messages/PicoBadge.tsx | 7 +- frontend/src/components/messages/TipLink.tsx | 4 +- frontend/src/components/preview/CodeTab.tsx | 12 +- .../src/components/preview/PreviewPane.tsx | 14 +- .../components/recording/ScreenRecorder.tsx | 18 ++- .../components/select-and-edit/EditPopup.tsx | 6 +- .../SelectAndEditModeToggleButton.tsx | 7 +- .../settings/ModelSettingsSection.tsx | 6 +- .../settings/OutputSettingsSection.tsx | 8 +- .../components/settings/SettingsDialog.tsx | 59 ++++--- frontend/src/components/sidebar/Sidebar.tsx | 26 ++-- 26 files changed, 1147 insertions(+), 130 deletions(-) create mode 100644 frontend/public/locales/de/translation.json create mode 100644 frontend/public/locales/fr/translation.json create mode 100644 frontend/public/locales/ja/translation.json create mode 100644 frontend/public/locales/ko/translation.json create mode 100644 frontend/public/locales/ru/translation.json diff --git a/frontend/public/locales/de/translation.json b/frontend/public/locales/de/translation.json new file mode 100644 index 00000000..151505b2 --- /dev/null +++ b/frontend/public/locales/de/translation.json @@ -0,0 +1,145 @@ +{ + "app": { + "title": "Screenshot zu Code", + "errors": { + "emptyInstruction": "Bitte geben Sie Anweisungen für die KI zur Aktualisierung an.", + "noCurrentVersion": "Keine aktuelle Version festgelegt. Kontaktieren Sie den Support oder öffnen Sie ein GitHub-Issue.", + "invalidHistory": "Ungültige Versionsverlauf. Dies sollte nicht passieren. Bitte kontaktieren Sie den Support oder öffnen Sie ein GitHub-Issue." + } + }, + "history": { + "versions": "Versionen", + "toggle": "Umschalten", + "fullPrompt": "Vollständige Eingabeaufforderung", + "create": "Erstellen", + "edit": "Bearbeiten", + "importedFromCode": "Aus Code importiert" + }, + "messages": { + "deprecationMessage": "Wir unterstützen dieses Modell nicht mehr. Stattdessen wird die Codegenerierung GPT-4o oder Claude Sonnet 3.5 verwenden, die zwei modernsten Modelle.", + "onboardingNote": { + "intro": "Um Screenshot zu Code zu nutzen,", + "buyCredits": "kaufen Sie Credits (100 Generierungen für $36)", + "orUseKey": "oder verwenden Sie Ihren eigenen OpenAI API-Schlüssel mit GPT4-Vision-Zugang.", + "followInstructions": "Folgen Sie diesen Anweisungen, um einen Schlüssel zu erhalten.", + "pasteKey": "und fügen Sie ihn in den Einstellungen-Dialog ein (Zahnrad-Symbol oben). Ihr Schlüssel wird nur in Ihrem Browser gespeichert. Niemals auf unseren Servern." + }, + "picoBadge": { + "featureRequests": "Feature-Wünsche?", + "openSourceProject": "ein Open-Source-Projekt von Pico" + }, + "tipLink": { + "text": "Tipps für bessere Ergebnisse" + } + }, + "preview": { + "codeTab": { + "copyCode": "Code kopieren", + "openIn": "Öffnen in", + "codeCopied": "In die Zwischenablage kopiert" + }, + "pane": { + "reset": "Zurücksetzen", + "download": "Herunterladen", + "desktop": "Desktop", + "mobile": "Mobil", + "code": "Code" + } + }, + "recording": { + "start": "Bildschirm aufnehmen", + "inProgress": "Aufnahme läuft...", + "finish": "Aufnahme beenden", + "captured": "Bildschirmaufnahme erfasst.", + "reRecord": "Erneut aufnehmen", + "generate": "Generieren", + "errorStarting": "Bildschirmaufnahme konnte nicht gestartet werden", + "noDataError": "Bildschirmaufnahme existiert nicht. Bitte versuchen Sie es erneut." + }, + "sidebar": { + "videoGenerationWarning": "Die Codegenerierung aus Videos kann 3-4 Minuten dauern. Wir machen mehrere Durchläufe, um das beste Ergebnis zu erzielen. Bitte haben Sie Geduld.", + "cancel": "Abbrechen", + "updateInstructionPlaceholder": "Sagen Sie der KI, was geändert werden soll...", + "includeScreenshot": "Screenshot der aktuellen Version einschließen?", + "update": "Aktualisieren", + "regenerate": "Neu generieren", + "referenceImageAlt": "Referenz", + "originalVideo": "Originalvideo", + "originalScreenshot": "Original-Screenshot", + "console": "Konsole" + }, + "selectAndEdit": { + "editPopup": { + "placeholder": "Sagen Sie der KI, was an diesem Element geändert werden soll...", + "update": "Aktualisieren" + }, + "selectAndEdit": { + "exitMode": "Auswahlmodus beenden", + "enterMode": "Auswählen und aktualisieren" + } + }, + "setting": { + "title": "Einstellungen", + "dallePlaceholder": "DALL-E Platzhalter-Bildgenerierung", + "dallePlaceholderDescription": "Mehr Spaß damit, aber wenn Sie Geld sparen möchten, schalten Sie es aus.", + "openAIApiKey": "OpenAI API-Schlüssel", + "apiKeyDescription": "Nur in Ihrem Browser gespeichert. Niemals auf Servern gespeichert. Überschreibt Ihre .env-Konfiguration.", + "openAIApiKeyPlaceholder": "OpenAI API-Schlüssel", + "openAIBaseURL": "OpenAI Basis-URL (optional)", + "openAIBaseURLDescription": "Ersetzen Sie durch eine Proxy-URL, wenn Sie die Standardeinstellung nicht verwenden möchten.", + "openAIBaseURLPlaceholder": "OpenAI Basis-URL", + "anthropicApiKey": "Anthropic API-Schlüssel", + "anthropicApiKeyPlaceholder": "Anthropic API-Schlüssel", + "language": "Sprache", + "screenshotConfig": "Screenshot-Konfiguration per URL", + "screenshotDescription": "Wenn Sie URLs direkt verwenden möchten, anstatt selbst Screenshots zu machen, fügen Sie einen ScreenshotOne API-Schlüssel hinzu.", + "getFreeScreenshots": "Erhalten Sie 100 Screenshots/Monat kostenlos.", + "screenshotApiKeyPlaceholder": "ScreenshotOne API-Schlüssel", + "themeSettings": "Theme-Einstellungen", + "appTheme": "App-Theme", + "toggleDarkMode": "Dunkelmodus umschalten", + "codeEditorTheme": "Code-Editor-Theme - erfordert Seitenaktualisierung zur Aktualisierung", + "save": "Speichern", + "aiModel": "KI-Modell:", + "beta": "Beta", + "generating": "Generierung:" + }, + "imageUpload": { + "dragDropPrompt": "Screenshot hier per Drag & Drop ablegen, \n oder klicken Sie zum Hochladen", + "new": "Neu!", + "uploadPrompt": "Laden Sie eine Bildschirmaufnahme (.mp4, .mov) hoch oder nehmen Sie Ihren Bildschirm auf, um eine ganze App zu klonen (experimentell).", + "learnMore": "Erfahren Sie mehr.", + "errorReading": "Fehler beim Lesen der Dateien: " + }, + "importCode": { + "buttonText": "Aus Code importieren", + "dialogTitle": "Fügen Sie Ihren HTML-Code ein", + "dialogDescription": "Stellen Sie sicher, dass der zu importierende Code gültiges HTML ist.", + "textareaPlaceholder": "Fügen Sie Ihren HTML-Code hier ein...", + "stackLabel": "Stack:", + "importButton": "Importieren", + "errorEmptyCode": "Bitte fügen Sie Code ein", + "errorNoStack": "Bitte wählen Sie Ihren Stack aus" + }, + "termsOfService": { + "title": "Geben Sie Ihre E-Mail-Adresse ein, um zu beginnen", + "emailPlaceholder": "E-Mail", + "consentText": "Durch die Angabe Ihrer E-Mail-Adresse stimmen Sie dem Erhalt gelegentlicher Produkt-Updates zu und akzeptieren die", + "termsLink": "Nutzungsbedingungen", + "localRunText": "Möchten Sie es lieber lokal ausführen? Dieses Projekt ist Open Source.", + "githubLink": "Laden Sie den Code herunter und beginnen Sie auf Github.", + "emailError": "Bitte geben Sie Ihre E-Mail-Adresse ein", + "agreeButton": "Zustimmen & Fortfahren", + "usageText": "Designer und Ingenieure dieser Organisationen verwenden Screenshot zu Code, um Schnittstellen schneller zu erstellen." + }, + "urlInput": { + "errorNoApiKey": "Bitte fügen Sie einen ScreenshotOne API-Schlüssel in den Einstellungen-Dialog hinzu. Dies ist optional - Sie können auch Bilder direkt per Drag & Drop hochladen.", + "errorNoUrl": "Bitte geben Sie eine URL ein", + "errorCapture": "Screenshot konnte nicht erfasst werden", + "errorCaptureDetails": "Screenshot konnte nicht erfasst werden. Schauen Sie in die Konsole und Ihre Backend-Logs für weitere Details.", + "orScreenshot": "Oder Screenshot einer URL...", + "enterUrl": "URL eingeben", + "capturing": "Erfassung läuft...", + "capture": "Erfassen" + } + } \ No newline at end of file diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index fd751be4..9132a9b6 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -6,5 +6,140 @@ "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." } + }, + "history": { + "versions": "Versions", + "toggle": "Toggle", + "fullPrompt": "Full prompt", + "create": "Create", + "edit": "Edit", + "importedFromCode": "Imported from code" + }, + "messages": { + "deprecationMessage": "We no longer support this model. Instead, code generation will use GPT-4o or Claude Sonnet 3.5, the 2 state-of-the-art models.", + "onboardingNote": { + "intro": "To use Screenshot to Code,", + "buyCredits": "buy some credits (100 generations for $36)", + "orUseKey": "or use your own OpenAI API key with GPT4 vision access.", + "followInstructions": "Follow these instructions to get yourself a key.", + "pasteKey": "and paste it in the Settings dialog (gear icon above). Your key is only stored in your browser. Never stored on our servers." + }, + "picoBadge": { + "featureRequests": "feature requests?", + "openSourceProject": "an open source project by Pico" + }, + "tipLink": { + "text": "Tips for better results" + } + }, + "preview": { + "codeTab": { + "copyCode": "Copy Code", + "openIn": "Open in", + "codeCopied": "Copied to clipboard" + }, + "pane": { + "reset": "Reset", + "download": "Download", + "desktop": "Desktop", + "mobile": "Mobile", + "code": "Code" + } + }, + "recording": { + "start": "Record Screen", + "inProgress": "Recording...", + "finish": "Finish Recording", + "captured": "Screen Recording Captured.", + "reRecord": "Re-record", + "generate": "Generate", + "errorStarting": "Could not start screen recording", + "noDataError": "Screen recording does not exist. Please try again." + }, + "sidebar": { + "videoGenerationWarning": "Code generation from videos can take 3-4 minutes. We do multiple passes to get the best result. Please be patient.", + "cancel": "Cancel", + "updateInstructionPlaceholder": "Tell the AI what to change...", + "includeScreenshot": "Include screenshot of current version?", + "update": "Update", + "regenerate": "Regenerate", + "referenceImageAlt": "Reference", + "originalVideo": "Original Video", + "originalScreenshot": "Original Screenshot", + "console": "Console" + }, + "selectAndEdit": { + "editPopup": { + "placeholder": "Tell the AI what to change about this element...", + "update": "Update" + }, + "selectAndEdit": { + "exitMode": "Exit selection mode", + "enterMode": "Select and update" + } + }, + "setting": { + "title": "Settings", + "dallePlaceholder": "DALL-E Placeholder Image Generation", + "dallePlaceholderDescription": "More fun with it but if you want to save money, turn it off.", + "openAIApiKey": "OpenAI API key", + "apiKeyDescription": "Only stored in your browser. Never stored on servers. Overrides your .env config.", + "openAIApiKeyPlaceholder": "OpenAI API key", + "openAIBaseURL": "OpenAI Base URL (optional)", + "openAIBaseURLDescription": "Replace with a proxy URL if you don't want to use the default.", + "openAIBaseURLPlaceholder": "OpenAI Base URL", + "anthropicApiKey": "Anthropic API key", + "anthropicApiKeyPlaceholder": "Anthropic API key", + "language": "Language", + "screenshotConfig": "Screenshot by URL Config", + "screenshotDescription": "If you want to use URLs directly instead of taking the screenshot yourself, add a ScreenshotOne API key.", + "getFreeScreenshots": "Get 100 screenshots/mo for free.", + "screenshotApiKeyPlaceholder": "ScreenshotOne API key", + "themeSettings": "Theme Settings", + "appTheme": "App Theme", + "toggleDarkMode": "Toggle dark mode", + "codeEditorTheme": "Code Editor Theme - requires page refresh to update", + "save": "Save", + "aiModel": "AI Model:", + "beta": "Beta", + "generating": "Generating:" + }, + "imageUpload": { + "dragDropPrompt": "Drag & drop a screenshot here, \n or click to upload", + "new": "New!", + "uploadPrompt": "Upload a screen recording (.mp4, .mov) or record your screen to clone a whole app (experimental).", + "learnMore": "Learn more.", + "errorReading": "Error reading files: " + }, + "importCode": { + "buttonText": "Import from Code", + "dialogTitle": "Paste in your HTML code", + "dialogDescription": "Make sure that the code you're importing is valid HTML.", + "textareaPlaceholder": "Paste your HTML code here...", + "stackLabel": "Stack:", + "importButton": "Import", + "errorEmptyCode": "Please paste in some code", + "errorNoStack": "Please select your stack" + }, + "termsOfService": { + "title": "Enter your email to get started", + "emailPlaceholder": "Email", + "consentText": "By providing your email, you consent to receiving occasional product updates, and you accept the", + "termsLink": "terms of service", + "localRunText": "Prefer to run it yourself locally? This project is open source.", + "githubLink": "Download the code and get started on Github.", + "emailError": "Please enter your email", + "agreeButton": "Agree & Continue", + "usageText": "Designers and engineers from these organizations use Screenshot to Code to build interfaces faster." + }, + "urlInput": { + "errorNoApiKey": "Please add a ScreenshotOne API key in the Settings dialog. This is optional - you can also drag/drop and upload images directly.", + "errorNoUrl": "Please enter a URL", + "errorCapture": "Failed to capture screenshot", + "errorCaptureDetails": "Failed to capture screenshot. Look at the console and your backend logs for more details.", + "orScreenshot": "Or screenshot a URL...", + "enterUrl": "Enter URL", + "capturing": "Capturing...", + "capture": "Capture" } } \ No newline at end of file diff --git a/frontend/public/locales/fr/translation.json b/frontend/public/locales/fr/translation.json new file mode 100644 index 00000000..3b6635e8 --- /dev/null +++ b/frontend/public/locales/fr/translation.json @@ -0,0 +1,145 @@ +{ + "app": { + "title": "Screenshot vers Code", + "errors": { + "emptyInstruction": "Veuillez inclure des instructions pour l'IA sur ce qu'il faut mettre à jour.", + "noCurrentVersion": "Aucune version actuelle définie. Contactez le support ou ouvrez un problème sur Github.", + "invalidHistory": "L'historique des versions est invalide. Cela ne devrait pas se produire. Veuillez contacter le support ou ouvrir un problème sur Github." + } + }, + "history": { + "versions": "Versions", + "toggle": "Basculer", + "fullPrompt": "Prompt complet", + "create": "Créer", + "edit": "Modifier", + "importedFromCode": "Importé du code" + }, + "messages": { + "deprecationMessage": "Nous ne prenons plus en charge ce modèle. À la place, la génération de code utilisera GPT-4o ou Claude Sonnet 3.5, les 2 modèles de pointe.", + "onboardingNote": { + "intro": "Pour utiliser Screenshot vers Code,", + "buyCredits": "achetez des crédits (100 générations pour 36$)", + "orUseKey": "ou utilisez votre propre clé API OpenAI avec accès à GPT4 vision.", + "followInstructions": "Suivez ces instructions pour obtenir une clé.", + "pasteKey": "et collez-la dans la boîte de dialogue des paramètres (icône d'engrenage ci-dessus). Votre clé est uniquement stockée dans votre navigateur. Jamais stockée sur nos serveurs." + }, + "picoBadge": { + "featureRequests": "demandes de fonctionnalités ?", + "openSourceProject": "un projet open source par Pico" + }, + "tipLink": { + "text": "Astuces pour de meilleurs résultats" + } + }, + "preview": { + "codeTab": { + "copyCode": "Copier le code", + "openIn": "Ouvrir dans", + "codeCopied": "Copié dans le presse-papiers" + }, + "pane": { + "reset": "Réinitialiser", + "download": "Télécharger", + "desktop": "Bureau", + "mobile": "Mobile", + "code": "Code" + } + }, + "recording": { + "start": "Enregistrer l'écran", + "inProgress": "Enregistrement...", + "finish": "Terminer l'enregistrement", + "captured": "Enregistrement d'écran capturé.", + "reRecord": "Réenregistrer", + "generate": "Générer", + "errorStarting": "Impossible de démarrer l'enregistrement d'écran", + "noDataError": "L'enregistrement d'écran n'existe pas. Veuillez réessayer." + }, + "sidebar": { + "videoGenerationWarning": "La génération de code à partir de vidéos peut prendre 3 à 4 minutes. Nous effectuons plusieurs passages pour obtenir le meilleur résultat. Veuillez patienter.", + "cancel": "Annuler", + "updateInstructionPlaceholder": "Dites à l'IA ce qu'il faut changer...", + "includeScreenshot": "Inclure une capture d'écran de la version actuelle ?", + "update": "Mettre à jour", + "regenerate": "Régénérer", + "referenceImageAlt": "Référence", + "originalVideo": "Vidéo originale", + "originalScreenshot": "Capture d'écran originale", + "console": "Console" + }, + "selectAndEdit": { + "editPopup": { + "placeholder": "Dites à l'IA ce qu'il faut changer à propos de cet élément...", + "update": "Mettre à jour" + }, + "selectAndEdit": { + "exitMode": "Quitter le mode de sélection", + "enterMode": "Sélectionner et mettre à jour" + } + }, + "setting": { + "title": "Paramètres", + "dallePlaceholder": "Génération d'images de remplacement DALL-E", + "dallePlaceholderDescription": "Plus amusant avec, mais si vous voulez économiser de l'argent, désactivez-le.", + "openAIApiKey": "Clé API OpenAI", + "apiKeyDescription": "Stockée uniquement dans votre navigateur. Jamais stockée sur les serveurs. Remplace votre configuration .env.", + "openAIApiKeyPlaceholder": "Clé API OpenAI", + "openAIBaseURL": "URL de base OpenAI (optionnel)", + "openAIBaseURLDescription": "Remplacez par une URL proxy si vous ne voulez pas utiliser la valeur par défaut.", + "openAIBaseURLPlaceholder": "URL de base OpenAI", + "anthropicApiKey": "Clé API Anthropic", + "anthropicApiKeyPlaceholder": "Clé API Anthropic", + "language": "Langue", + "screenshotConfig": "Configuration de capture d'écran par URL", + "screenshotDescription": "Si vous voulez utiliser directement des URLs au lieu de prendre vous-même la capture d'écran, ajoutez une clé API ScreenshotOne.", + "getFreeScreenshots": "Obtenez 100 captures d'écran/mois gratuitement.", + "screenshotApiKeyPlaceholder": "Clé API ScreenshotOne", + "themeSettings": "Paramètres du thème", + "appTheme": "Thème de l'application", + "toggleDarkMode": "Basculer en mode sombre", + "codeEditorTheme": "Thème de l'éditeur de code - nécessite un rafraîchissement de la page pour mettre à jour", + "save": "Enregistrer", + "aiModel": "Modèle d'IA :", + "beta": "Bêta", + "generating": "Génération :" + }, + "imageUpload": { + "dragDropPrompt": "Glissez et déposez une capture d'écran ici, \n ou cliquez pour télécharger", + "new": "Nouveau !", + "uploadPrompt": "Téléchargez un enregistrement d'écran (.mp4, .mov) ou enregistrez votre écran pour cloner une application entière (expérimental).", + "learnMore": "En savoir plus.", + "errorReading": "Erreur de lecture des fichiers : " + }, + "importCode": { + "buttonText": "Importer depuis le code", + "dialogTitle": "Collez votre code HTML", + "dialogDescription": "Assurez-vous que le code que vous importez est du HTML valide.", + "textareaPlaceholder": "Collez votre code HTML ici...", + "stackLabel": "Stack :", + "importButton": "Importer", + "errorEmptyCode": "Veuillez coller du code", + "errorNoStack": "Veuillez sélectionner votre stack" + }, + "termsOfService": { + "title": "Entrez votre e-mail pour commencer", + "emailPlaceholder": "E-mail", + "consentText": "En fournissant votre e-mail, vous consentez à recevoir des mises à jour occasionnelles du produit et vous acceptez les", + "termsLink": "conditions d'utilisation", + "localRunText": "Préférez-vous l'exécuter localement ? Ce projet est open source.", + "githubLink": "Téléchargez le code et commencez sur Github.", + "emailError": "Veuillez entrer votre e-mail", + "agreeButton": "Accepter et continuer", + "usageText": "Les designers et ingénieurs de ces organisations utilisent Screenshot vers Code pour construire des interfaces plus rapidement." + }, + "urlInput": { + "errorNoApiKey": "Veuillez ajouter une clé API ScreenshotOne dans la boîte de dialogue des paramètres. C'est optionnel - vous pouvez également glisser-déposer et télécharger des images directement.", + "errorNoUrl": "Veuillez entrer une URL", + "errorCapture": "Échec de la capture d'écran", + "errorCaptureDetails": "Échec de la capture d'écran. Consultez la console et vos logs backend pour plus de détails.", + "orScreenshot": "Ou capture d'écran d'une URL...", + "enterUrl": "Entrer l'URL", + "capturing": "Capture en cours...", + "capture": "Capturer" + } + } \ No newline at end of file diff --git a/frontend/public/locales/ja/translation.json b/frontend/public/locales/ja/translation.json new file mode 100644 index 00000000..e48248f8 --- /dev/null +++ b/frontend/public/locales/ja/translation.json @@ -0,0 +1,145 @@ +{ + "app": { + "title": "スクリーンショットからコードへ", + "errors": { + "emptyInstruction": "AIに更新内容を指示してください。", + "noCurrentVersion": "現在のバージョンが設定されていません。サポートに連絡するかGithubでイシューを開いてください。", + "invalidHistory": "バージョン履歴が無効です。これは起こるべきではありません。サポートに連絡するかGithubでイシューを開いてください。" + } + }, + "history": { + "versions": "バージョン", + "toggle": "切り替え", + "fullPrompt": "完全なプロンプト", + "create": "作成", + "edit": "編集", + "importedFromCode": "コードからインポート" + }, + "messages": { + "deprecationMessage": "このモデルはもうサポートしていません。代わりに、コード生成にはGPT-4oまたはClaude Sonnet 3.5という最先端の2つのモデルを使用します。", + "onboardingNote": { + "intro": "Screenshot to Codeを使用するには、", + "buyCredits": "クレジットを購入するか(100回の生成で36ドル)", + "orUseKey": "またはGPT4ビジョンアクセス付きの自分のOpenAI APIキーを使用してください。", + "followInstructions": "キーを取得するにはこれらの指示に従ってください。", + "pasteKey": "そして設定ダイアログ(上部の歯車アイコン)にペーストしてください。キーはブラウザにのみ保存され、サーバーには決して保存されません。" + }, + "picoBadge": { + "featureRequests": "機能リクエスト?", + "openSourceProject": "Picoによるオープンソースプロジェクト" + }, + "tipLink": { + "text": "より良い結果を得るためのヒント" + } + }, + "preview": { + "codeTab": { + "copyCode": "コードをコピー", + "openIn": "で開く", + "codeCopied": "クリップボードにコピーしました" + }, + "pane": { + "reset": "リセット", + "download": "ダウンロード", + "desktop": "デスクトップ", + "mobile": "モバイル", + "code": "コード" + } + }, + "recording": { + "start": "画面録画", + "inProgress": "録画中...", + "finish": "録画終了", + "captured": "画面録画が完了しました。", + "reRecord": "再録画", + "generate": "生成", + "errorStarting": "画面録画を開始できませんでした", + "noDataError": "画面録画が存在しません。もう一度お試しください。" + }, + "sidebar": { + "videoGenerationWarning": "ビデオからのコード生成には3〜4分かかる場合があります。最良の結果を得るために複数回のパスを行います。お待ちください。", + "cancel": "キャンセル", + "updateInstructionPlaceholder": "AIに変更内容を指示してください...", + "includeScreenshot": "現在のバージョンのスクリーンショットを含めますか?", + "update": "更新", + "regenerate": "再生成", + "referenceImageAlt": "参照", + "originalVideo": "元のビデオ", + "originalScreenshot": "元のスクリーンショット", + "console": "コンソール" + }, + "selectAndEdit": { + "editPopup": { + "placeholder": "この要素について変更したい内容をAIに指示してください...", + "update": "更新" + }, + "selectAndEdit": { + "exitMode": "選択モードを終了", + "enterMode": "選択して更新" + } + }, + "setting": { + "title": "設定", + "dallePlaceholder": "DALL-Eプレースホルダー画像生成", + "dallePlaceholderDescription": "オンにすると楽しいですが、お金を節約したい場合はオフにしてください。", + "openAIApiKey": "OpenAI APIキー", + "apiKeyDescription": "ブラウザにのみ保存されます。サーバーには決して保存されません。.env設定よりも優先されます。", + "openAIApiKeyPlaceholder": "OpenAI APIキー", + "openAIBaseURL": "OpenAIベースURL(オプション)", + "openAIBaseURLDescription": "デフォルトを使用したくない場合は、プロキシURLに置き換えてください。", + "openAIBaseURLPlaceholder": "OpenAIベースURL", + "anthropicApiKey": "Anthropic APIキー", + "anthropicApiKeyPlaceholder": "Anthropic APIキー", + "language": "言語", + "screenshotConfig": "URLによるスクリーンショット設定", + "screenshotDescription": "スクリーンショットを自分で撮る代わりにURLを直接使用したい場合は、ScreenshotOne APIキーを追加してください。", + "getFreeScreenshots": "月100枚のスクリーンショットを無料で取得。", + "screenshotApiKeyPlaceholder": "ScreenshotOne APIキー", + "themeSettings": "テーマ設定", + "appTheme": "アプリテーマ", + "toggleDarkMode": "ダークモード切り替え", + "codeEditorTheme": "コードエディターテーマ - 更新にはページの再読み込みが必要です", + "save": "保存", + "aiModel": "AIモデル:", + "beta": "ベータ", + "generating": "生成中:" + }, + "imageUpload": { + "dragDropPrompt": "スクリーンショットをここにドラッグ&ドロップするか、\nクリックしてアップロードしてください", + "new": "新機能!", + "uploadPrompt": "画面録画(.mp4、.mov)をアップロードするか、画面を録画してアプリ全体をクローンします(実験的)。", + "learnMore": "詳細はこちら。", + "errorReading": "ファイルの読み込みエラー: " + }, + "importCode": { + "buttonText": "コードからインポート", + "dialogTitle": "HTMLコードを貼り付けてください", + "dialogDescription": "インポートするコードが有効なHTMLであることを確認してください。", + "textareaPlaceholder": "ここにHTMLコードを貼り付けてください...", + "stackLabel": "スタック:", + "importButton": "インポート", + "errorEmptyCode": "コードを貼り付けてください", + "errorNoStack": "スタックを選択してください" + }, + "termsOfService": { + "title": "開始するにはメールアドレスを入力してください", + "emailPlaceholder": "メールアドレス", + "consentText": "メールアドレスを提供することで、製品更新の不定期な受信に同意し、", + "termsLink": "利用規約", + "localRunText": "ローカルで実行することを希望しますか?このプロジェクトはオープンソースです。", + "githubLink": "Githubでコードをダウンロードして始めましょう。", + "emailError": "メールアドレスを入力してください", + "agreeButton": "同意して続行", + "usageText": "これらの組織のデザイナーとエンジニアは、インターフェースをより速く構築するためにScreenshot to Codeを使用しています。" + }, + "urlInput": { + "errorNoApiKey": "設定ダイアログでScreenshotOne APIキーを追加してください。これはオプションです - 画像を直接ドラッグ&ドロップしてアップロードすることもできます。", + "errorNoUrl": "URLを入力してください", + "errorCapture": "スクリーンショットの取得に失敗しました", + "errorCaptureDetails": "スクリーンショットの取得に失敗しました。詳細については、コンソールとバックエンドログを確認してください。", + "orScreenshot": "またはURLのスクリーンショットを撮影...", + "enterUrl": "URLを入力", + "capturing": "取得中...", + "capture": "取得" + } + } \ No newline at end of file diff --git a/frontend/public/locales/ko/translation.json b/frontend/public/locales/ko/translation.json new file mode 100644 index 00000000..c5f341f0 --- /dev/null +++ b/frontend/public/locales/ko/translation.json @@ -0,0 +1,145 @@ +{ + "app": { + "title": "스크린샷에서 코드로", + "errors": { + "emptyInstruction": "AI에게 업데이트할 내용에 대한 지시사항을 포함해 주세요.", + "noCurrentVersion": "현재 버전이 설정되지 않았습니다. 지원팀에 문의하거나 Github 이슈를 열어주세요.", + "invalidHistory": "버전 기록이 유효하지 않습니다. 이런 일은 일어나지 않아야 합니다. 지원팀에 문의하거나 Github 이슈를 열어주세요." + } + }, + "history": { + "versions": "버전", + "toggle": "토글", + "fullPrompt": "전체 프롬프트", + "create": "생성", + "edit": "편집", + "importedFromCode": "코드에서 가져옴" + }, + "messages": { + "deprecationMessage": "우리는 더 이상 이 모델을 지원하지 않습니다. 대신, 코드 생성은 GPT-4o 또는 Claude Sonnet 3.5, 최첨단 모델 2개를 사용할 것입니다.", + "onboardingNote": { + "intro": "Screenshot to Code를 사용하려면,", + "buyCredits": "크레딧을 구매하거나 (100회 생성에 $36)", + "orUseKey": "또는 GPT4 비전 액세스가 있는 자신의 OpenAI API 키를 사용하세요.", + "followInstructions": "키를 얻으려면 이 지침을 따르세요.", + "pasteKey": "그리고 설정 대화상자(위의 기어 아이콘)에 붙여넣으세요. 키는 브라우저에만 저장됩니다. 우리 서버에는 절대 저장되지 않습니다." + }, + "picoBadge": { + "featureRequests": "기능 요청?", + "openSourceProject": "Pico의 오픈 소스 프로젝트" + }, + "tipLink": { + "text": "더 나은 결과를 위한 팁" + } + }, + "preview": { + "codeTab": { + "copyCode": "코드 복사", + "openIn": "열기", + "codeCopied": "클립보드에 복사됨" + }, + "pane": { + "reset": "초기화", + "download": "다운로드", + "desktop": "데스크톱", + "mobile": "모바일", + "code": "코드" + } + }, + "recording": { + "start": "화면 녹화", + "inProgress": "녹화 중...", + "finish": "녹화 종료", + "captured": "화면 녹화가 캡처되었습니다.", + "reRecord": "다시 녹화", + "generate": "생성", + "errorStarting": "화면 녹화를 시작할 수 없습니다", + "noDataError": "화면 녹화가 존재하지 않습니다. 다시 시도해 주세요." + }, + "sidebar": { + "videoGenerationWarning": "비디오에서 코드 생성은 3-4분이 걸릴 수 있습니다. 최상의 결과를 얻기 위해 여러 번의 패스를 수행합니다. 기다려 주세요.", + "cancel": "취소", + "updateInstructionPlaceholder": "AI에게 변경할 내용을 알려주세요...", + "includeScreenshot": "현재 버전의 스크린샷을 포함하시겠습니까?", + "update": "업데이트", + "regenerate": "재생성", + "referenceImageAlt": "참조", + "originalVideo": "원본 비디오", + "originalScreenshot": "원본 스크린샷", + "console": "콘솔" + }, + "selectAndEdit": { + "editPopup": { + "placeholder": "AI에게 이 요소에 대해 변경할 내용을 알려주세요...", + "update": "업데이트" + }, + "selectAndEdit": { + "exitMode": "선택 모드 종료", + "enterMode": "선택 및 업데이트" + } + }, + "setting": { + "title": "설정", + "dallePlaceholder": "DALL-E 플레이스홀더 이미지 생성", + "dallePlaceholderDescription": "켜면 더 재미있지만 돈을 절약하고 싶다면 끄세요.", + "openAIApiKey": "OpenAI API 키", + "apiKeyDescription": "브라우저에만 저장됩니다. 서버에는 절대 저장되지 않습니다. .env 설정을 덮어씁니다.", + "openAIApiKeyPlaceholder": "OpenAI API 키", + "openAIBaseURL": "OpenAI 기본 URL (선택사항)", + "openAIBaseURLDescription": "기본값을 사용하지 않으려면 프록시 URL로 대체하세요.", + "openAIBaseURLPlaceholder": "OpenAI 기본 URL", + "anthropicApiKey": "Anthropic API 키", + "anthropicApiKeyPlaceholder": "Anthropic API 키", + "language": "언어", + "screenshotConfig": "URL로 스크린샷 설정", + "screenshotDescription": "직접 스크린샷을 찍는 대신 URL을 직접 사용하려면 ScreenshotOne API 키를 추가하세요.", + "getFreeScreenshots": "월 100장의 무료 스크린샷을 받으세요.", + "screenshotApiKeyPlaceholder": "ScreenshotOne API 키", + "themeSettings": "테마 설정", + "appTheme": "앱 테마", + "toggleDarkMode": "다크 모드 전환", + "codeEditorTheme": "코드 에디터 테마 - 업데이트하려면 페이지 새로고침 필요", + "save": "저장", + "aiModel": "AI 모델:", + "beta": "베타", + "generating": "생성 중:" + }, + "imageUpload": { + "dragDropPrompt": "스크린샷을 여기에 드래그 앤 드롭하거나,\n클릭하여 업로드하세요", + "new": "새로운!", + "uploadPrompt": "화면 녹화 (.mp4, .mov)를 업로드하거나 화면을 녹화하여 전체 앱을 복제하세요 (실험적).", + "learnMore": "자세히 알아보기.", + "errorReading": "파일 읽기 오류: " + }, + "importCode": { + "buttonText": "코드에서 가져오기", + "dialogTitle": "HTML 코드를 붙여넣으세요", + "dialogDescription": "가져오려는 코드가 유효한 HTML인지 확인하세요.", + "textareaPlaceholder": "여기에 HTML 코드를 붙여넣으세요...", + "stackLabel": "스택:", + "importButton": "가져오기", + "errorEmptyCode": "코드를 붙여넣어 주세요", + "errorNoStack": "스택을 선택해 주세요" + }, + "termsOfService": { + "title": "시작하려면 이메일을 입력하세요", + "emailPlaceholder": "이메일", + "consentText": "이메일을 제공함으로써, 가끔의 제품 업데이트 수신에 동의하며,", + "termsLink": "서비스 약관", + "localRunText": "로컬에서 직접 실행하고 싶으신가요? 이 프로젝트는 오픈 소스입니다.", + "githubLink": "Github에서 코드를 다운로드하고 시작하세요.", + "emailError": "이메일을 입력해 주세요", + "agreeButton": "동의 및 계속", + "usageText": "이 조직들의 디자이너와 엔지니어들이 Screenshot to Code를 사용하여 더 빠르게 인터페이스를 구축합니다." + }, + "urlInput": { + "errorNoApiKey": "설정 대화상자에 ScreenshotOne API 키를 추가해 주세요. 이는 선택사항입니다 - 이미지를 직접 드래그/드롭하여 업로드할 수도 있습니다.", + "errorNoUrl": "URL을 입력해 주세요", + "errorCapture": "스크린샷 캡처 실패", + "errorCaptureDetails": "스크린샷 캡처에 실패했습니다. 자세한 내용은 콘솔과 백엔드 로그를 확인하세요.", + "orScreenshot": "또는 URL의 스크린샷 찍기...", + "enterUrl": "URL 입력", + "capturing": "캡처 중...", + "capture": "캡처" + } + } \ No newline at end of file diff --git a/frontend/public/locales/ru/translation.json b/frontend/public/locales/ru/translation.json new file mode 100644 index 00000000..7086cef6 --- /dev/null +++ b/frontend/public/locales/ru/translation.json @@ -0,0 +1,145 @@ +{ + "app": { + "title": "Скриншот в код", + "errors": { + "emptyInstruction": "Пожалуйста, включите инструкции для ИИ о том, что обновить.", + "noCurrentVersion": "Текущая версия не установлена. Свяжитесь с поддержкой или откройте проблему на Github.", + "invalidHistory": "История версий недействительна. Этого не должно происходить. Пожалуйста, свяжитесь с поддержкой или откройте проблему на Github." + } + }, + "history": { + "versions": "Версии", + "toggle": "Переключить", + "fullPrompt": "Полный запрос", + "create": "Создать", + "edit": "Редактировать", + "importedFromCode": "Импортировано из кода" + }, + "messages": { + "deprecationMessage": "Мы больше не поддерживаем эту модель. Вместо этого генерация кода будет использовать GPT-4o или Claude Sonnet 3.5, две самые современные модели.", + "onboardingNote": { + "intro": "Чтобы использовать Screenshot to Code,", + "buyCredits": "купите кредиты (100 генераций за $36)", + "orUseKey": "или используйте свой собственный ключ API OpenAI с доступом к GPT4 vision.", + "followInstructions": "Следуйте этим инструкциям, чтобы получить ключ.", + "pasteKey": "и вставьте его в диалог настроек (значок шестеренки вверху). Ваш ключ хранится только в вашем браузере. Никогда не хранится на наших серверах." + }, + "picoBadge": { + "featureRequests": "запросы функций?", + "openSourceProject": "проект с открытым исходным кодом от Pico" + }, + "tipLink": { + "text": "Советы для лучших результатов" + } + }, + "preview": { + "codeTab": { + "copyCode": "Копировать код", + "openIn": "Открыть в", + "codeCopied": "Скопировано в буфер обмена" + }, + "pane": { + "reset": "Сбросить", + "download": "Скачать", + "desktop": "Десктоп", + "mobile": "Мобильный", + "code": "Код" + } + }, + "recording": { + "start": "Записать экран", + "inProgress": "Запись...", + "finish": "Завершить запись", + "captured": "Запись экрана захвачена.", + "reRecord": "Перезаписать", + "generate": "Сгенерировать", + "errorStarting": "Не удалось начать запись экрана", + "noDataError": "Запись экрана не существует. Пожалуйста, попробуйте еще раз." + }, + "sidebar": { + "videoGenerationWarning": "Генерация кода из видео может занять 3-4 минуты. Мы делаем несколько проходов для получения наилучшего результата. Пожалуйста, будьте терпеливы.", + "cancel": "Отмена", + "updateInstructionPlaceholder": "Скажите ИИ, что изменить...", + "includeScreenshot": "Включить скриншот текущей версии?", + "update": "Обновить", + "regenerate": "Перегенерировать", + "referenceImageAlt": "Ссылка", + "originalVideo": "Оригинальное видео", + "originalScreenshot": "Оригинальный скриншот", + "console": "Консоль" + }, + "selectAndEdit": { + "editPopup": { + "placeholder": "Скажите ИИ, что изменить в этом элементе...", + "update": "Обновить" + }, + "selectAndEdit": { + "exitMode": "Выйти из режима выбора", + "enterMode": "Выбрать и обновить" + } + }, + "setting": { + "title": "Настройки", + "dallePlaceholder": "Генерация заполнителей изображений DALL-E", + "dallePlaceholderDescription": "С ней веселее, но если хотите сэкономить деньги, выключите.", + "openAIApiKey": "Ключ API OpenAI", + "apiKeyDescription": "Хранится только в вашем браузере. Никогда не хранится на серверах. Переопределяет вашу конфигурацию .env.", + "openAIApiKeyPlaceholder": "Ключ API OpenAI", + "openAIBaseURL": "Базовый URL OpenAI (необязательно)", + "openAIBaseURLDescription": "Замените прокси-URL, если не хотите использовать по умолчанию.", + "openAIBaseURLPlaceholder": "Базовый URL OpenAI", + "anthropicApiKey": "Ключ API Anthropic", + "anthropicApiKeyPlaceholder": "Ключ API Anthropic", + "language": "Язык", + "screenshotConfig": "Конфигурация скриншота по URL", + "screenshotDescription": "Если вы хотите использовать URL напрямую вместо самостоятельного создания скриншотов, добавьте ключ API ScreenshotOne.", + "getFreeScreenshots": "Получите 100 скриншотов/мес бесплатно.", + "screenshotApiKeyPlaceholder": "Ключ API ScreenshotOne", + "themeSettings": "Настройки темы", + "appTheme": "Тема приложения", + "toggleDarkMode": "Переключить темный режим", + "codeEditorTheme": "Тема редактора кода - требуется обновление страницы для обновления", + "save": "Сохранить", + "aiModel": "Модель ИИ:", + "beta": "Бета", + "generating": "Генерация:" + }, + "imageUpload": { + "dragDropPrompt": "Перетащите скриншот сюда, \n или нажмите для загрузки", + "new": "Новое!", + "uploadPrompt": "Загрузите запись экрана (.mp4, .mov) или запишите свой экран, чтобы клонировать целое приложение (экспериментально).", + "learnMore": "Узнать больше.", + "errorReading": "Ошибка чтения файлов: " + }, + "importCode": { + "buttonText": "Импорт из кода", + "dialogTitle": "Вставьте ваш HTML-код", + "dialogDescription": "Убедитесь, что импортируемый код является валидным HTML.", + "textareaPlaceholder": "Вставьте ваш HTML-код здесь...", + "stackLabel": "Стек:", + "importButton": "Импорт", + "errorEmptyCode": "Пожалуйста, вставьте код", + "errorNoStack": "Пожалуйста, выберите ваш стек" + }, + "termsOfService": { + "title": "Введите свой email, чтобы начать", + "emailPlaceholder": "Email", + "consentText": "Предоставляя свой email, вы соглашаетесь получать периодические обновления продукта и принимаете", + "termsLink": "условия использования", + "localRunText": "Предпочитаете запускать локально? Этот проект с открытым исходным кодом.", + "githubLink": "Скачайте код и начните на Github.", + "emailError": "Пожалуйста, введите ваш email", + "agreeButton": "Согласиться и продолжить", + "usageText": "Дизайнеры и инженеры из этих организаций используют Screenshot to Code для более быстрого создания интерфейсов." + }, + "urlInput": { + "errorNoApiKey": "Пожалуйста, добавьте ключ API ScreenshotOne в диалог настроек. Это необязательно - вы также можете напрямую перетаскивать и загружать изображения.", + "errorNoUrl": "Пожалуйста, введите URL", + "errorCapture": "Не удалось захватить скриншот", + "errorCaptureDetails": "Не удалось захватить скриншот. Проверьте консоль и журналы бэкенда для получения дополнительной информации.", + "orScreenshot": "Или скриншот URL...", + "enterUrl": "Введите URL", + "capturing": "Захват...", + "capture": "Захватить" + } + } \ No newline at end of file diff --git a/frontend/public/locales/zh/translation.json b/frontend/public/locales/zh/translation.json index 93ac6214..09849fa4 100644 --- a/frontend/public/locales/zh/translation.json +++ b/frontend/public/locales/zh/translation.json @@ -6,5 +6,140 @@ "noCurrentVersion": "未设置当前版本。请联系支持或在Github上开issue。", "invalidHistory": "版本历史无效。这不应该发生。请联系支持或在Github上开issue。" } + }, + "history": { + "versions": "版本", + "toggle": "切换", + "fullPrompt": "完整提示", + "create": "创建", + "edit": "编辑", + "importedFromCode": "从代码导入" + }, + "messages": { + "deprecationMessage": "我们不再支持此模型。相反,代码生成将使用 GPT-4o 或 Claude Sonnet 3.5,这两个是最先进的模型。", + "onboardingNote": { + "intro": "要使用截图转代码功能,", + "buyCredits": "购买一些积分(36美元100次生成)", + "orUseKey": "或使用您自己的OpenAI API密钥(需要GPT4 vision访问权限)。", + "followInstructions": "按照这些说明获取密钥。", + "pasteKey": "然后将其粘贴到设置对话框中(上方的齿轮图标)。您的密钥仅存储在您的浏览器中,绝不会存储在我们的服务器上。" + }, + "picoBadge": { + "featureRequests": "功能请求?", + "openSourceProject": "Pico 的开源项目" + }, + "tipLink": { + "text": "获取更好结果的提示" + } + }, + "preview": { + "codeTab": { + "copyCode": "复制代码", + "openIn": "在以下打开", + "codeCopied": "已复制到剪贴板" + }, + "pane": { + "reset": "重置", + "download": "下载", + "desktop": "桌面", + "mobile": "移动", + "code": "代码" + } + }, + "recording": { + "start": "录制屏幕", + "inProgress": "录制中...", + "finish": "结束录制", + "captured": "屏幕录制已完成。", + "reRecord": "重新录制", + "generate": "生成", + "errorStarting": "无法开始屏幕录制", + "noDataError": "屏幕录制不存在。请重试。" + }, + "sidebar": { + "videoGenerationWarning": "从视频生成代码可能需要3-4分钟。我们会进行多次处理以获得最佳结果。请耐心等待。", + "cancel": "取消", + "updateInstructionPlaceholder": "告诉AI要改变什么...", + "includeScreenshot": "包含当前版本的截图?", + "update": "更新", + "regenerate": "重新生成", + "referenceImageAlt": "参考图", + "originalVideo": "原始视频", + "originalScreenshot": "原始截图", + "console": "控制台" + }, + "selectAndEdit": { + "editPopup": { + "placeholder": "告诉 AI 要如何改变这个元素...", + "update": "更新" + }, + "selectAndEdit": { + "exitMode": "退出选择模式", + "enterMode": "选择并更新" + } + }, + "setting": { + "title": "设置", + "dallePlaceholder": "DALL-E 占位图像生成", + "dallePlaceholderDescription": "使用它会更有趣,但如果您想节省成本,可以关闭它。", + "openAIApiKey": "OpenAI API 密钥", + "apiKeyDescription": "仅存储在您的浏览器中。绝不存储在服务器上。覆盖您的 .env 配置。", + "openAIApiKeyPlaceholder": "OpenAI API 密钥", + "openAIBaseURL": "OpenAI 基础 URL(可选)", + "openAIBaseURLDescription": "如果您不想使用默认值,请替换为代理 URL。", + "openAIBaseURLPlaceholder": "OpenAI 基础 URL", + "anthropicApiKey": "Anthropic API 密钥", + "anthropicApiKeyPlaceholder": "Anthropic API 密钥", + "language": "语言", + "screenshotConfig": "URL 截图配置", + "screenshotDescription": "如果您想直接使用 URL 而不是自己截图,请添加 ScreenshotOne API 密钥。", + "getFreeScreenshots": "每月免费获取 100 张截图。", + "screenshotApiKeyPlaceholder": "ScreenshotOne API 密钥", + "themeSettings": "主题设置", + "appTheme": "应用主题", + "toggleDarkMode": "切换深色模式", + "codeEditorTheme": "代码编辑器主题 - 需要刷新页面才能更新", + "save": "保存", + "aiModel": "AI 模型:", + "beta": "测试版", + "generating": "生成:" + }, + "imageUpload": { + "dragDropPrompt": "将截图拖放到此处,\n 或点击上传", + "new": "新功能!", + "uploadPrompt": "上传屏幕录像(.mp4,.mov)或录制您的屏幕以克隆整个应用(实验性功能)。", + "learnMore": "了解更多。", + "errorReading": "读取文件时出错:" + }, + "importCode": { + "buttonText": "从代码导入", + "dialogTitle": "粘贴您的HTML代码", + "dialogDescription": "请确保您导入的代码是有效的HTML。", + "textareaPlaceholder": "在此粘贴您的HTML代码...", + "stackLabel": "技术栈:", + "importButton": "导入", + "errorEmptyCode": "请粘贴一些代码", + "errorNoStack": "请选择您的技术栈" + }, + "termsOfService": { + "title": "请输入您的电子邮箱以开始使用", + "emailPlaceholder": "电子邮箱", + "consentText": "提供您的电子邮箱即表示您同意接收偶尔的产品更新,并且您接受", + "termsLink": "服务条款", + "localRunText": "想在本地运行?这个项目是开源的。", + "githubLink": "在 Github 上下载代码并开始使用。", + "emailError": "请输入您的电子邮箱", + "agreeButton": "同意并继续", + "usageText": "这些组织的设计师和工程师使用 Screenshot to Code 来更快地构建界面。" + }, + "urlInput": { + "errorNoApiKey": "请在设置对话框中添加 ScreenshotOne API 密钥。这是可选的 - 您也可以直接拖放和上传图片。", + "errorNoUrl": "请输入 URL", + "errorCapture": "截图失败", + "errorCaptureDetails": "截图失败。请查看控制台和后端日志以获取更多详细信息。", + "orScreenshot": "或者截取 URL 的屏幕截图...", + "enterUrl": "输入 URL", + "capturing": "正在截图...", + "capture": "截图" } } \ No newline at end of file diff --git a/frontend/src/components/ImageUpload.tsx b/frontend/src/components/ImageUpload.tsx index bbecd922..7cc92f08 100644 --- a/frontend/src/components/ImageUpload.tsx +++ b/frontend/src/components/ImageUpload.tsx @@ -7,6 +7,7 @@ import { URLS } from "../urls"; import { Badge } from "./ui/badge"; import ScreenRecorder from "./recording/ScreenRecorder"; import { ScreenRecorderState } from "../types"; +import { useTranslation } from 'react-i18next'; const baseStyle = { flex: 1, @@ -62,6 +63,7 @@ interface Props { } function ImageUpload({ setReferenceImages }: Props) { + const { t } = useTranslation(); const [files, setFiles] = useState([]); // TODO: Switch to Zustand const [screenRecorderState, setScreenRecorderState] = @@ -109,7 +111,7 @@ function ImageUpload({ setReferenceImages }: Props) { }); }, onDropRejected: (rejectedFiles) => { - toast.error(rejectedFiles[0].errors[0].message); + toast.error(t('imageUpload.errorReading') + rejectedFiles[0].errors[0].message); }, }); @@ -168,21 +170,19 @@ function ImageUpload({ setReferenceImages }: Props) {

- Drag & drop a screenshot here,
- or click to upload + {t('imageUpload.dragDropPrompt')}

)} {screenRecorderState === ScreenRecorderState.INITIAL && (
- New! Upload a screen recording (.mp4, .mov) or record - your screen to clone a whole app (experimental).{" "} + {t('imageUpload.new')} {t('imageUpload.uploadPrompt')}{" "}{" "} - Learn more. + {t('imageUpload.learnMore')}
)} diff --git a/frontend/src/components/ImportCodeSection.tsx b/frontend/src/components/ImportCodeSection.tsx index 5628e958..7d9f0e37 100644 --- a/frontend/src/components/ImportCodeSection.tsx +++ b/frontend/src/components/ImportCodeSection.tsx @@ -13,23 +13,25 @@ import { Textarea } from "./ui/textarea"; import OutputSettingsSection from "./settings/OutputSettingsSection"; import toast from "react-hot-toast"; import { Stack } from "../lib/stacks"; +import { useTranslation } from 'react-i18next'; interface Props { importFromCode: (code: string, stack: Stack) => void; } function ImportCodeSection({ importFromCode }: Props) { + const { t } = useTranslation(); const [code, setCode] = useState(""); const [stack, setStack] = useState(undefined); const doImport = () => { if (code === "") { - toast.error("Please paste in some code"); + toast.error(t('importCode.errorEmptyCode')); return; } if (stack === undefined) { - toast.error("Please select your stack"); + toast.error(t('importCode.errorNoStack')); return; } @@ -39,14 +41,14 @@ function ImportCodeSection({ importFromCode }: Props) { - Paste in your HTML code + {t('importCode.dialogTitle')} - Make sure that the code you're importing is valid HTML. + {t('importCode.dialogDescription')} @@ -54,18 +56,19 @@ function ImportCodeSection({ importFromCode }: Props) { value={code} onChange={(e) => setCode(e.target.value)} className="w-full h-64" + placeholder={t('importCode.textareaPlaceholder')} /> setStack(config)} - label="Stack:" + label={t('importCode.stackLabel')} shouldDisableUpdates={false} /> diff --git a/frontend/src/components/TermsOfServiceDialog.tsx b/frontend/src/components/TermsOfServiceDialog.tsx index ad46c0a2..f3349ffe 100644 --- a/frontend/src/components/TermsOfServiceDialog.tsx +++ b/frontend/src/components/TermsOfServiceDialog.tsx @@ -10,6 +10,7 @@ import { import { Input } from "./ui/input"; import toast from "react-hot-toast"; import { PICO_BACKEND_FORM_SECRET } from "../config"; +import { useTranslation } from 'react-i18next'; const LOGOS = ["microsoft", "amazon", "mit", "stanford", "bytedance", "baidu"]; @@ -17,6 +18,7 @@ const TermsOfServiceDialog: React.FC<{ open: boolean; onOpenChange: (open: boolean) => void; }> = ({ open, onOpenChange }) => { + const { t } = useTranslation(); const [email, setEmail] = React.useState(""); const onSubscribe = async () => { @@ -34,13 +36,13 @@ const TermsOfServiceDialog: React.FC<{ - Enter your email to get started + {t('termsOfService.title')}
{ setEmail(e.target.value); @@ -49,27 +51,26 @@ const TermsOfServiceDialog: React.FC<{

- By providing your email, you consent to receiving occasional product - updates, and you accept the{" "} + {t('termsOfService.consentText')}{" "} - terms of service + {t('termsOfService.termsLink')} .{" "}

{" "} - Prefer to run it yourself locally? This project is open source.{" "} + {t('termsOfService.localRunText')}{" "} - Download the code and get started on Github. + {t('termsOfService.githubLink')}

@@ -79,13 +80,13 @@ const TermsOfServiceDialog: React.FC<{ onClick={(e) => { if (!email.trim() || !email.trim().includes("@")) { e.preventDefault(); - toast.error("Please enter your email"); + toast.error(t('termsOfService.emailError')); } else { onSubscribe(); } }} > - Agree & Continue + {t('termsOfService.agreeButton')} @@ -107,8 +108,7 @@ const TermsOfServiceDialog: React.FC<{ ))}
- Designers and engineers from these organizations use Screenshot to - Code to build interfaces faster. + {t('termsOfService.usageText')}
diff --git a/frontend/src/components/UrlInputSection.tsx b/frontend/src/components/UrlInputSection.tsx index 18422d61..d2be8abd 100644 --- a/frontend/src/components/UrlInputSection.tsx +++ b/frontend/src/components/UrlInputSection.tsx @@ -3,6 +3,7 @@ import { HTTP_BACKEND_URL } from "../config"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { toast } from "react-hot-toast"; +import { useTranslation } from 'react-i18next'; interface Props { screenshotOneApiKey: string | null; @@ -10,20 +11,18 @@ interface Props { } export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) { + const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const [referenceUrl, setReferenceUrl] = useState(""); async function takeScreenshot() { if (!screenshotOneApiKey) { - toast.error( - "Please add a ScreenshotOne API key in the Settings dialog. This is optional - you can also drag/drop and upload images directly.", - { duration: 8000 } - ); + toast.error(t('urlInput.errorNoApiKey'), { duration: 8000 }); return; } if (!referenceUrl) { - toast.error("Please enter a URL"); + toast.error(t('urlInput.errorNoUrl')); return; } @@ -42,16 +41,14 @@ export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) { }); if (!response.ok) { - throw new Error("Failed to capture screenshot"); + throw new Error(t('urlInput.errorCapture')); } const res = await response.json(); doCreate([res.url], "image"); } catch (error) { console.error(error); - toast.error( - "Failed to capture screenshot. Look at the console and your backend logs for more details." - ); + toast.error(t('urlInput.errorCaptureDetails')); } finally { setIsLoading(false); } @@ -60,9 +57,9 @@ export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) { return (
-
Or screenshot a URL...
+
{t('urlInput.orScreenshot')}
setReferenceUrl(e.target.value)} value={referenceUrl} /> @@ -71,7 +68,7 @@ export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) { disabled={isLoading} className="bg-slate-400 capture-btn" > - {isLoading ? "Capturing..." : "Capture"} + {isLoading ? t('urlInput.capturing') : t('urlInput.capture')}
); diff --git a/frontend/src/components/history/HistoryDisplay.tsx b/frontend/src/components/history/HistoryDisplay.tsx index a3dcdb1a..bdae80e9 100644 --- a/frontend/src/components/history/HistoryDisplay.tsx +++ b/frontend/src/components/history/HistoryDisplay.tsx @@ -11,6 +11,7 @@ import { import { Button } from "../ui/button"; import { CaretSortIcon } from "@radix-ui/react-icons"; import { useProjectStore } from "../../store/project-store"; +import { useTranslation } from 'react-i18next'; interface Props { shouldDisableReverts: boolean; @@ -24,6 +25,7 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) { setGeneratedCode, } = useProjectStore(); const renderedHistory = renderHistory(history, currentVersion); + const { t } = useTranslation(); const revertToVersion = (index: number) => { if (index < 0 || index >= history.length || !history[index]) return; @@ -33,7 +35,7 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) { return renderedHistory.length === 0 ? null : (
-

Versions

+

{t('history.versions')}

    {renderedHistory.map((item, index) => (
  • @@ -59,7 +61,7 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) { } >
    -

    {item.summary}

    +

    {t(item.summary)}

    {item.parentVersion !== null && (

    (parent: {item.parentVersion}) @@ -71,14 +73,14 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) {

    -
    Full prompt: {item.summary}
    +
    {t('history.fullPrompt')}: {t(item.summary)}
    - {item.type} + {t(item.type)}
    diff --git a/frontend/src/components/history/utils.ts b/frontend/src/components/history/utils.ts index 785c20bf..3b33b84c 100644 --- a/frontend/src/components/history/utils.ts +++ b/frontend/src/components/history/utils.ts @@ -39,11 +39,11 @@ export function extractHistoryTree( function displayHistoryItemType(itemType: HistoryItemType) { switch (itemType) { case "ai_create": - return "Create"; + return "history.create"; case "ai_edit": - return "Edit"; + return "history.edit"; case "code_create": - return "Imported from code"; + return "history.importedFromCode"; default: { const exhaustiveCheck: never = itemType; throw new Error(`Unhandled case: ${exhaustiveCheck}`); @@ -55,11 +55,11 @@ function summarizeHistoryItem(item: HistoryItem) { const itemType = item.type; switch (itemType) { case "ai_create": - return "Create"; + return "history.create"; case "ai_edit": return item.inputs.prompt; case "code_create": - return "Imported from code"; + return "history.importedFromCode"; default: { const exhaustiveCheck: never = itemType; throw new Error(`Unhandled case: ${exhaustiveCheck}`); diff --git a/frontend/src/components/messages/DeprecationMessage.tsx b/frontend/src/components/messages/DeprecationMessage.tsx index 840d4016..9be2fb36 100644 --- a/frontend/src/components/messages/DeprecationMessage.tsx +++ b/frontend/src/components/messages/DeprecationMessage.tsx @@ -1,16 +1,17 @@ import React from "react"; +import { useTranslation } from 'react-i18next'; interface DeprecationMessageProps {} const DeprecationMessage: React.FC = () => { + const { t } = useTranslation(); return (

    - We no longer support this model. Instead, code generation will use - GPT-4o or Claude Sonnet 3.5, the 2 state-of-the-art models. + {t('messages.deprecationMessage')}

    ); }; -export default DeprecationMessage; +export default DeprecationMessage; \ No newline at end of file diff --git a/frontend/src/components/messages/OnboardingNote.tsx b/frontend/src/components/messages/OnboardingNote.tsx index acf919f2..b81abc72 100644 --- a/frontend/src/components/messages/OnboardingNote.tsx +++ b/frontend/src/components/messages/OnboardingNote.tsx @@ -1,25 +1,27 @@ +import { useTranslation } from 'react-i18next'; + export function OnboardingNote() { + const { t } = useTranslation(); return (
    - To use Screenshot to Code,{" "} + {t('messages.onboardingNote.intro')}{" "} - buy some credits (100 generations for $36) + {t('messages.onboardingNote.buyCredits')} {" "} - or use your own OpenAI API key with GPT4 vision access.{" "} + {t('messages.onboardingNote.orUseKey')}{" "} - Follow these instructions to get yourself a key. + {t('messages.onboardingNote.followInstructions')} {" "} - and paste it in the Settings dialog (gear icon above). Your key is only - stored in your browser. Never stored on our servers. + {t('messagesonboardingNote.pasteKey')}
    ); diff --git a/frontend/src/components/messages/PicoBadge.tsx b/frontend/src/components/messages/PicoBadge.tsx index bdd00832..eee1f968 100644 --- a/frontend/src/components/messages/PicoBadge.tsx +++ b/frontend/src/components/messages/PicoBadge.tsx @@ -1,4 +1,7 @@ +import { useTranslation } from 'react-i18next'; + export function PicoBadge() { + const { t } = useTranslation(); return ( <> - feature requests? + {t('messages.picoBadge.featureRequests')}
@@ -17,7 +20,7 @@ export function PicoBadge() { className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black bg-white px-4 text-xs py-3 cursor-pointer" > - an open source project by Pico + {t('messages.picoBadge.openSourceProject')} diff --git a/frontend/src/components/messages/TipLink.tsx b/frontend/src/components/messages/TipLink.tsx index b54f83ce..422c9b85 100644 --- a/frontend/src/components/messages/TipLink.tsx +++ b/frontend/src/components/messages/TipLink.tsx @@ -1,6 +1,8 @@ +import { useTranslation } from 'react-i18next'; import { URLS } from "../../urls"; function TipLink() { + const { t } = useTranslation(); return ( - Tips for better results + {t('messages.tipLink.text')} ); } diff --git a/frontend/src/components/preview/CodeTab.tsx b/frontend/src/components/preview/CodeTab.tsx index 378d761f..97c8d75a 100644 --- a/frontend/src/components/preview/CodeTab.tsx +++ b/frontend/src/components/preview/CodeTab.tsx @@ -5,6 +5,7 @@ import { Settings } from "../../types"; import copy from "copy-to-clipboard"; import { useCallback } from "react"; import toast from "react-hot-toast"; +import { useTranslation } from 'react-i18next'; interface Props { code: string; @@ -13,10 +14,11 @@ interface Props { } function CodeTab({ code, setCode, settings }: Props) { + const { t } = useTranslation(); const copyCode = useCallback(() => { copy(code); - toast.success("Copied to clipboard"); - }, [code]); + toast.success(t('preview.codeTab.codeCopied')); + }, [code, t]); const doOpenInCodepenio = useCallback(async () => { // TODO: Update CSS and JS external links depending on the framework being used @@ -57,17 +59,17 @@ function CodeTab({ code, setCode, settings }: Props) {
- Copy Code + {t('preview.codeTab.copyCode')} )} @@ -57,14 +59,14 @@ function PreviewPane({ doUpdate, reset, settings }: Props) {
- Desktop + {t('preview.pane.desktop')} - Mobile + {t('preview.pane.mobile')} - Code + {t('preview.pane.code')}
@@ -95,4 +97,4 @@ function PreviewPane({ doUpdate, reset, settings }: Props) { ); } -export default PreviewPane; +export default PreviewPane; \ No newline at end of file diff --git a/frontend/src/components/recording/ScreenRecorder.tsx b/frontend/src/components/recording/ScreenRecorder.tsx index a0318884..da46549b 100644 --- a/frontend/src/components/recording/ScreenRecorder.tsx +++ b/frontend/src/components/recording/ScreenRecorder.tsx @@ -4,6 +4,7 @@ import { ScreenRecorderState } from "../../types"; import { blobToBase64DataUrl } from "./utils"; import fixWebmDuration from "webm-duration-fix"; import toast from "react-hot-toast"; +import { useTranslation } from 'react-i18next'; interface Props { screenRecorderState: ScreenRecorderState; @@ -19,6 +20,7 @@ function ScreenRecorder({ setScreenRecorderState, generateCode, }: Props) { + const { t } = useTranslation(); const [mediaStream, setMediaStream] = useState(null); const [mediaRecorder, setMediaRecorder] = useState( null @@ -66,7 +68,7 @@ function ScreenRecorder({ mediaRecorder.start(); setScreenRecorderState(ScreenRecorderState.RECORDING); } catch (error) { - toast.error("Could not start screen recording"); + toast.error(t('recording.errorStarting')); throw error; } }; @@ -90,7 +92,7 @@ function ScreenRecorder({ if (screenRecordingDataUrl) { generateCode([screenRecordingDataUrl], "video"); } else { - toast.error("Screen recording does not exist. Please try again."); + toast.error(t('recording.noDataError')); throw new Error("No screen recording data url"); } }; @@ -98,23 +100,23 @@ function ScreenRecorder({ return (
{screenRecorderState === ScreenRecorderState.INITIAL && ( - + )} {screenRecorderState === ScreenRecorderState.RECORDING && (
- Recording... + {t('recording.inProgress')}
- +
)} {screenRecorderState === ScreenRecorderState.FINISHED && (
- Screen Recording Captured. + {t('recording.captured')}
{screenRecordingDataUrl && (
)} diff --git a/frontend/src/components/select-and-edit/EditPopup.tsx b/frontend/src/components/select-and-edit/EditPopup.tsx index aab67443..a4b6c983 100644 --- a/frontend/src/components/select-and-edit/EditPopup.tsx +++ b/frontend/src/components/select-and-edit/EditPopup.tsx @@ -4,6 +4,7 @@ import { Button } from "../ui/button"; import { addHighlight, getAdjustedCoordinates, removeHighlight } from "./utils"; import { useAppStore } from "../../store/app-store"; import KeyboardShortcutBadge from "../core/KeyboardShortcutBadge"; +import { useTranslation } from 'react-i18next'; interface EditPopupProps { event: MouseEvent | null; @@ -16,6 +17,7 @@ const EditPopup: React.FC = ({ iframeRef, doUpdate, }) => { + const { t } = useTranslation(); // App state const { inSelectAndEditMode } = useAppStore(); @@ -126,7 +128,7 @@ const EditPopup: React.FC = ({ ref={textareaRef} value={updateText} onChange={(e) => setUpdateText(e.target.value)} - placeholder="Tell the AI what to change about this element..." + placeholder={t('selectAndEdit.editPopup.placeholder')} className="dark:bg-gray-700 dark:text-white" onKeyDown={(e) => { if (e.key === "Enter") { @@ -140,7 +142,7 @@ const EditPopup: React.FC = ({ className="dark:bg-gray-700 dark:text-white" onClick={() => onUpdate(updateText)} > - Update + {t('selectAndEdit.editPopup.update')}
diff --git a/frontend/src/components/select-and-edit/SelectAndEditModeToggleButton.tsx b/frontend/src/components/select-and-edit/SelectAndEditModeToggleButton.tsx index 2f76486d..1c456296 100644 --- a/frontend/src/components/select-and-edit/SelectAndEditModeToggleButton.tsx +++ b/frontend/src/components/select-and-edit/SelectAndEditModeToggleButton.tsx @@ -1,10 +1,11 @@ import { GiClick } from "react-icons/gi"; import { useAppStore } from "../../store/app-store"; import { Button } from "../ui/button"; +import { useTranslation } from 'react-i18next'; function SelectAndEditModeToggleButton() { const { inSelectAndEditMode, toggleInSelectAndEditMode } = useAppStore(); - + const { t } = useTranslation(); return ( ); diff --git a/frontend/src/components/settings/ModelSettingsSection.tsx b/frontend/src/components/settings/ModelSettingsSection.tsx index 314dfc53..80c7368f 100644 --- a/frontend/src/components/settings/ModelSettingsSection.tsx +++ b/frontend/src/components/settings/ModelSettingsSection.tsx @@ -10,6 +10,7 @@ import { CodeGenerationModel, } from "../../lib/models"; import { Badge } from "../ui/badge"; +import { useTranslation } from 'react-i18next'; interface Props { codeGenerationModel: CodeGenerationModel; @@ -22,10 +23,11 @@ function ModelSettingsSection({ setCodeGenerationModel, shouldDisableUpdates = false, }: Props) { + const { t } = useTranslation(); return (
- AI Model: + {t('setting.aiModel')} setStack(value as Stack)} @@ -57,7 +59,7 @@ function OutputSettingsSection({ {generateDisplayComponent(stack)} {STACK_DESCRIPTIONS[stack].inBeta && ( - Beta + {t('setting.beta')} )}
diff --git a/frontend/src/components/settings/SettingsDialog.tsx b/frontend/src/components/settings/SettingsDialog.tsx index 9bbc59a2..f0f3712c 100644 --- a/frontend/src/components/settings/SettingsDialog.tsx +++ b/frontend/src/components/settings/SettingsDialog.tsx @@ -23,6 +23,7 @@ import { AccordionTrigger, } from "../ui/accordion"; import LanguageSelector from "../languageSelector/LanguageSelector"; +import { useTranslation } from 'react-i18next'; interface Props { settings: Settings; @@ -30,6 +31,7 @@ interface Props { } function SettingsDialog({ settings, setSettings }: Props) { + const { t } = useTranslation(); const handleThemeChange = (theme: EditorTheme) => { setSettings((s) => ({ ...s, @@ -44,14 +46,14 @@ function SettingsDialog({ settings, setSettings }: Props) { - Settings + {t('setting.title')}
setSettings((s) => ({ @@ -91,15 +92,15 @@ function SettingsDialog({ settings, setSettings }: Props) { {!IS_RUNNING_ON_CLOUD && (
setSettings((s) => ({ @@ -113,16 +114,15 @@ function SettingsDialog({ settings, setSettings }: Props) {
setSettings((s) => ({ @@ -133,23 +133,22 @@ function SettingsDialog({ settings, setSettings }: Props) { />
- - -
+ + +
- Screenshot by URL Config + {t('setting.screenshotConfig')} @@ -157,7 +156,7 @@ function SettingsDialog({ settings, setSettings }: Props) { setSettings((s) => ({ @@ -172,11 +171,11 @@ function SettingsDialog({ settings, setSettings }: Props) { - Theme Settings + {t('setting.themeSettings')}
-