Add international content and adapt internationally

This commit is contained in:
wsh 2024-08-17 22:56:10 +08:00
parent 658e0a497f
commit d2ddb2d261
26 changed files with 1147 additions and 130 deletions

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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": "取得"
}
}

View File

@ -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": "캡처"
}
}

View File

@ -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": "Захватить"
}
}

View File

@ -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": "截图"
}
}

View File

@ -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<FileWithPreview[]>([]);
// 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) {
<div {...getRootProps({ style: style as any })}>
<input {...getInputProps()} className="file-input" />
<p className="text-slate-700 text-lg">
Drag & drop a screenshot here, <br />
or click to upload
{t('imageUpload.dragDropPrompt')}
</p>
</div>
)}
{screenRecorderState === ScreenRecorderState.INITIAL && (
<div className="text-center text-sm text-slate-800 mt-4">
<Badge>New!</Badge> Upload a screen recording (.mp4, .mov) or record
your screen to clone a whole app (experimental).{" "}
<Badge>{t('imageUpload.new')}</Badge> {t('imageUpload.uploadPrompt')}{" "}{" "}
<a
className="underline"
href={URLS["intro-to-video"]}
target="_blank"
>
Learn more.
{t('imageUpload.learnMore')}
</a>
</div>
)}

View File

@ -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<Stack | undefined>(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) {
<Dialog>
<DialogTrigger asChild>
<Button className="import-from-code-btn" variant="secondary">
Import from Code
{t('importCode.buttonText')}
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Paste in your HTML code</DialogTitle>
<DialogTitle>{t('importCode.dialogTitle')}</DialogTitle>
<DialogDescription>
Make sure that the code you're importing is valid HTML.
{t('importCode.dialogDescription')}
</DialogDescription>
</DialogHeader>
@ -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')}
/>
<OutputSettingsSection
stack={stack}
setStack={(config: Stack) => setStack(config)}
label="Stack:"
label={t('importCode.stackLabel')}
shouldDisableUpdates={false}
/>
<DialogFooter>
<Button className="import-btn" type="submit" onClick={doImport}>
Import
{t('importCode.importButton')}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -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<{
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="mb-2 text-xl">
Enter your email to get started
{t('termsOfService.title')}
</AlertDialogTitle>
</AlertDialogHeader>
<div className="mb-2">
<Input
placeholder="Email"
placeholder={t('termsOfService.emailPlaceholder')}
value={email}
onChange={(e) => {
setEmail(e.target.value);
@ -49,27 +51,26 @@ const TermsOfServiceDialog: React.FC<{
</div>
<div className="flex flex-col space-y-3 text-sm">
<p>
By providing your email, you consent to receiving occasional product
updates, and you accept the{" "}
{t('termsOfService.consentText')}{" "}
<a
href="https://a.picoapps.xyz/camera-write"
target="_blank"
className="underline"
>
terms of service
{t('termsOfService.termsLink')}
</a>
.{" "}
</p>
<p>
{" "}
Prefer to run it yourself locally? This project is open source.{" "}
{t('termsOfService.localRunText')}{" "}
<a
href="https://github.com/abi/screenshot-to-code"
target="_blank"
className="underline"
>
Download the code and get started on Github.
{t('termsOfService.githubLink')}
</a>
</p>
</div>
@ -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')}
</AlertDialogAction>
</AlertDialogFooter>
@ -107,8 +108,7 @@ const TermsOfServiceDialog: React.FC<{
))}
</div>
<div className="text-gray-500 text-xs mt-4 text-center">
Designers and engineers from these organizations use Screenshot to
Code to build interfaces faster.
{t('termsOfService.usageText')}
</div>
</div>
</AlertDialogContent>

View File

@ -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 (
<div className="max-w-[90%] min-w-[40%] gap-y-2 flex flex-col">
<div className="text-gray-500 text-sm">Or screenshot a URL...</div>
<div className="text-gray-500 text-sm">{t('urlInput.orScreenshot')}</div>
<Input
placeholder="Enter URL"
placeholder={t('urlInput.enterUrl')}
onChange={(e) => 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')}
</Button>
</div>
);

View File

@ -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 : (
<div className="flex flex-col h-screen">
<h1 className="font-bold mb-2">Versions</h1>
<h1 className="font-bold mb-2">{t('history.versions')}</h1>
<ul className="space-y-0 flex flex-col-reverse">
{renderedHistory.map((item, index) => (
<li key={index}>
@ -59,7 +61,7 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) {
}
>
<div className="flex gap-x-1 truncate">
<h2 className="text-sm truncate">{item.summary}</h2>
<h2 className="text-sm truncate">{t(item.summary)}</h2>
{item.parentVersion !== null && (
<h2 className="text-sm">
(parent: {item.parentVersion})
@ -71,14 +73,14 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) {
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="h-6">
<CaretSortIcon className="h-4 w-4" />
<span className="sr-only">Toggle</span>
<span className="sr-only">{t('historytoggle')}</span>
</Button>
</CollapsibleTrigger>
</div>
<CollapsibleContent className="w-full bg-slate-300 p-2">
<div>Full prompt: {item.summary}</div>
<div>{t('history.fullPrompt')}: {t(item.summary)}</div>
<div className="flex justify-end">
<Badge>{item.type}</Badge>
<Badge>{t(item.type)}</Badge>
</div>
</CollapsibleContent>
</Collapsible>

View File

@ -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}`);

View File

@ -1,16 +1,17 @@
import React from "react";
import { useTranslation } from 'react-i18next';
interface DeprecationMessageProps {}
const DeprecationMessage: React.FC<DeprecationMessageProps> = () => {
const { t } = useTranslation();
return (
<div className="rounded-lg p-2 bg-fuchsia-200">
<p className="text-gray-800 text-sm">
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')}
</p>
</div>
);
};
export default DeprecationMessage;
export default DeprecationMessage;

View File

@ -1,25 +1,27 @@
import { useTranslation } from 'react-i18next';
export function OnboardingNote() {
const { t } = useTranslation();
return (
<div className="flex flex-col space-y-4 bg-green-700 p-2 rounded text-stone-200 text-sm">
<span>
To use Screenshot to Code,{" "}
{t('messages.onboardingNote.intro')}{" "}
<a
className="inline underline hover:opacity-70"
href="https://buy.stripe.com/8wM6sre70gBW1nqaEE"
target="_blank"
>
buy some credits (100 generations for $36)
{t('messages.onboardingNote.buyCredits')}
</a>{" "}
or use your own OpenAI API key with GPT4 vision access.{" "}
{t('messages.onboardingNote.orUseKey')}{" "}
<a
href="https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
className="inline underline hover:opacity-70"
target="_blank"
>
Follow these instructions to get yourself a key.
{t('messages.onboardingNote.followInstructions')}
</a>{" "}
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')}
</span>
</div>
);

View File

@ -1,4 +1,7 @@
import { useTranslation } from 'react-i18next';
export function PicoBadge() {
const { t } = useTranslation();
return (
<>
<a
@ -9,7 +12,7 @@ export function PicoBadge() {
className="fixed z-50 bottom-16 right-5 rounded-md shadow bg-black
text-white px-4 text-xs py-3 cursor-pointer"
>
feature requests?
{t('messages.picoBadge.featureRequests')}
</div>
</a>
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
@ -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')}
</div>
</a>
</>

View File

@ -1,6 +1,8 @@
import { useTranslation } from 'react-i18next';
import { URLS } from "../../urls";
function TipLink() {
const { t } = useTranslation();
return (
<a
className="text-xs underline text-gray-500 text-right"
@ -8,7 +10,7 @@ function TipLink() {
target="_blank"
rel="noopener"
>
Tips for better results
{t('messages.tipLink.text')}
</a>
);
}

View File

@ -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) {
<div className="relative">
<div className="flex justify-start items-center px-4 mb-2">
<span
title="Copy Code"
title={t('preview.copyCode')}
className="bg-black text-white flex items-center justify-center hover:text-black hover:bg-gray-100 cursor-pointer rounded-lg text-sm p-2.5"
onClick={copyCode}
>
Copy Code <FaCopy className="ml-2" />
{t('preview.codeTab.copyCode')} <FaCopy className="ml-2" />
</span>
<Button
onClick={doOpenInCodepenio}
className="bg-gray-100 text-black ml-2 py-2 px-4 border border-black rounded-md hover:bg-gray-400 focus:outline-none"
>
Open in{" "}
{t('preview.codeTab.openIn')}{" "}
<img
src="https://assets.codepen.io/t-1/codepen-logo.svg"
alt="codepen.io"

View File

@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../ui/tabs";
import {
FaUndo,
@ -22,6 +23,7 @@ interface Props {
}
function PreviewPane({ doUpdate, reset, settings }: Props) {
const { t } = useTranslation();
const { appState } = useAppStore();
const { inputMode, generatedCode, setGeneratedCode } = useProjectStore();
@ -42,14 +44,14 @@ function PreviewPane({ doUpdate, reset, settings }: Props) {
className="flex items-center ml-4 gap-x-2 dark:text-white dark:bg-gray-700"
>
<FaUndo />
Reset
{t('preview.pane.reset')}
</Button>
<Button
onClick={() => downloadCode(generatedCode)}
variant="secondary"
className="flex items-center gap-x-2 mr-4 dark:text-white dark:bg-gray-700 download-btn"
>
<FaDownload /> Download
<FaDownload /> {t('preview.pane.download')}
</Button>
</>
)}
@ -57,14 +59,14 @@ function PreviewPane({ doUpdate, reset, settings }: Props) {
<div className="flex items-center">
<TabsList>
<TabsTrigger value="desktop" className="flex gap-x-2">
<FaDesktop /> Desktop
<FaDesktop /> {t('preview.pane.desktop')}
</TabsTrigger>
<TabsTrigger value="mobile" className="flex gap-x-2">
<FaMobile /> Mobile
<FaMobile /> {t('preview.pane.mobile')}
</TabsTrigger>
<TabsTrigger value="code" className="flex gap-x-2">
<FaCode />
Code
{t('preview.pane.code')}
</TabsTrigger>
</TabsList>
</div>
@ -95,4 +97,4 @@ function PreviewPane({ doUpdate, reset, settings }: Props) {
);
}
export default PreviewPane;
export default PreviewPane;

View File

@ -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<MediaStream | null>(null);
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(
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 (
<div className="flex items-center justify-center my-3">
{screenRecorderState === ScreenRecorderState.INITIAL && (
<Button onClick={startScreenRecording}>Record Screen</Button>
<Button onClick={startScreenRecording}>{t('recording.start')}</Button>
)}
{screenRecorderState === ScreenRecorderState.RECORDING && (
<div className="flex items-center flex-col gap-y-4">
<div className="flex items-center mr-2 text-xl gap-x-1">
<span className="block h-10 w-10 bg-red-600 rounded-full mr-1 animate-pulse"></span>
<span>Recording...</span>
<span>{t('recording.inProgress')}</span>
</div>
<Button onClick={stopScreenRecording}>Finish Recording</Button>
<Button onClick={stopScreenRecording}>{t('recording.finish')}</Button>
</div>
)}
{screenRecorderState === ScreenRecorderState.FINISHED && (
<div className="flex items-center flex-col gap-y-4">
<div className="flex items-center mr-2 text-xl gap-x-1">
<span>Screen Recording Captured.</span>
<span>{t('recording.captured')}</span>
</div>
{screenRecordingDataUrl && (
<video
@ -132,9 +134,9 @@ function ScreenRecorder({
setScreenRecorderState(ScreenRecorderState.INITIAL)
}
>
Re-record
{t('recording.reRecord')}
</Button>
<Button onClick={kickoffGeneration}>Generate</Button>
<Button onClick={kickoffGeneration}>{t('recording.generate')}</Button>
</div>
</div>
)}

View File

@ -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<EditPopupProps> = ({
iframeRef,
doUpdate,
}) => {
const { t } = useTranslation();
// App state
const { inSelectAndEditMode } = useAppStore();
@ -126,7 +128,7 @@ const EditPopup: React.FC<EditPopupProps> = ({
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<EditPopupProps> = ({
className="dark:bg-gray-700 dark:text-white"
onClick={() => onUpdate(updateText)}
>
Update <KeyboardShortcutBadge letter="enter" />
{t('selectAndEdit.editPopup.update')} <KeyboardShortcutBadge letter="enter" />
</Button>
</div>
</div>

View File

@ -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 (
<Button
onClick={toggleInSelectAndEditMode}
@ -13,7 +14,9 @@ function SelectAndEditModeToggleButton() {
>
<GiClick className="text-lg" />
<span>
{inSelectAndEditMode ? "Exit selection mode" : "Select and update"}
{inSelectAndEditMode
? t('selectAndEdit.selectAndEdit.exitMode')
: t('selectAndEdit.selectAndEdit.enterMode')}
</span>
</Button>
);

View File

@ -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 (
<div className="flex flex-col gap-y-2 justify-between text-sm">
<div className="grid grid-cols-3 items-center gap-4">
<span>AI Model:</span>
<span>{t('setting.aiModel')}</span>
<Select
value={codeGenerationModel}
onValueChange={(value: string) =>
@ -48,7 +50,7 @@ function ModelSettingsSection({
</span>
{CODE_GENERATION_MODEL_DESCRIPTIONS[model].inBeta && (
<Badge className="ml-2" variant="secondary">
Beta
{t('setting.beta')}
</Badge>
)}
</div>

View File

@ -8,6 +8,7 @@ import {
} from "../ui/select";
import { Badge } from "../ui/badge";
import { Stack, STACK_DESCRIPTIONS } from "../../lib/stacks";
import { useTranslation } from 'react-i18next';
function generateDisplayComponent(stack: Stack) {
const stackComponents = STACK_DESCRIPTIONS[stack].components;
@ -34,13 +35,14 @@ interface Props {
function OutputSettingsSection({
stack,
setStack,
label = "Generating:",
label = "setting.generating",
shouldDisableUpdates = false,
}: Props) {
const { t } = useTranslation();
return (
<div className="flex flex-col gap-y-2 justify-between text-sm">
<div className="grid grid-cols-3 items-center gap-4">
<span>{label}</span>
<span>{t(label)}</span>
<Select
value={stack}
onValueChange={(value: string) => setStack(value as Stack)}
@ -57,7 +59,7 @@ function OutputSettingsSection({
{generateDisplayComponent(stack)}
{STACK_DESCRIPTIONS[stack].inBeta && (
<Badge className="ml-2" variant="secondary">
Beta
{t('setting.beta')}
</Badge>
)}
</div>

View File

@ -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) {
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle className="mb-4">Settings</DialogTitle>
<DialogTitle className="mb-4">{t('setting.title')}</DialogTitle>
</DialogHeader>
<div className="flex items-center space-x-2">
<Label htmlFor="image-generation">
<div>DALL-E Placeholder Image Generation</div>
<div>{t('setting.dallePlaceholder')}</div>
<div className="font-light mt-2 text-xs">
More fun with it but if you want to save money, turn it off.
{t('setting.dallePlaceholderDescription')}
</div>
</Label>
<Switch
@ -68,16 +70,15 @@ function SettingsDialog({ settings, setSettings }: Props) {
<div className="flex flex-col space-y-6">
<div>
<Label htmlFor="openai-api-key">
<div>OpenAI API key</div>
<div>{t('setting.openAIApiKey')}</div>
<div className="font-light mt-1 mb-2 text-xs leading-relaxed">
Only stored in your browser. Never stored on servers. Overrides
your .env config.
{t('setting.apiKeyDescription')}
</div>
</Label>
<Input
id="openai-api-key"
placeholder="OpenAI API key"
placeholder={t('setting.openAIApiKeyPlaceholder')}
value={settings.openAiApiKey || ""}
onChange={(e) =>
setSettings((s) => ({
@ -91,15 +92,15 @@ function SettingsDialog({ settings, setSettings }: Props) {
{!IS_RUNNING_ON_CLOUD && (
<div>
<Label htmlFor="openai-api-key">
<div>OpenAI Base URL (optional)</div>
<div>{t('setting.openAIBaseURL')}</div>
<div className="font-light mt-2 leading-relaxed">
Replace with a proxy URL if you don't want to use the default.
{t('setting.openAIBaseURLDescription')}
</div>
</Label>
<Input
id="openai-base-url"
placeholder="OpenAI Base URL"
placeholder={t('setting.openAIBaseURLPlaceholder')}
value={settings.openAiBaseURL || ""}
onChange={(e) =>
setSettings((s) => ({
@ -113,16 +114,15 @@ function SettingsDialog({ settings, setSettings }: Props) {
<div>
<Label htmlFor="anthropic-api-key">
<div>Anthropic API key</div>
<div>{t('setting.anthropicApiKey')}</div>
<div className="font-light mt-1 text-xs leading-relaxed">
Only stored in your browser. Never stored on servers. Overrides
your .env config.
{t('setting.apiKeyDescription')}
</div>
</Label>
<Input
id="anthropic-api-key"
placeholder="Anthropic API key"
placeholder={t('setting.anthropicApiKeyPlaceholder')}
value={settings.anthropicApiKey || ""}
onChange={(e) =>
setSettings((s) => ({
@ -133,23 +133,22 @@ function SettingsDialog({ settings, setSettings }: Props) {
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="language-select">Language</Label>
<LanguageSelector />
</div>
<Label htmlFor="language-select">{t('setting.language')}</Label>
<LanguageSelector />
</div>
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>Screenshot by URL Config</AccordionTrigger>
<AccordionTrigger>{t('setting.screenshotConfig')}</AccordionTrigger>
<AccordionContent>
<Label htmlFor="screenshot-one-api-key">
<div className="leading-normal font-normal text-xs">
If you want to use URLs directly instead of taking the
screenshot yourself, add a ScreenshotOne API key.{" "}
{t('setting.screenshotDescription')}{" "}
<a
href="https://screenshotone.com?via=screenshot-to-code"
className="underline"
target="_blank"
>
Get 100 screenshots/mo for free.
{t('setting.getFreeScreenshots')}
</a>
</div>
</Label>
@ -157,7 +156,7 @@ function SettingsDialog({ settings, setSettings }: Props) {
<Input
id="screenshot-one-api-key"
className="mt-2"
placeholder="ScreenshotOne API key"
placeholder={t('setting.screenshotApiKeyPlaceholder')}
value={settings.screenshotOneApiKey || ""}
onChange={(e) =>
setSettings((s) => ({
@ -172,11 +171,11 @@ function SettingsDialog({ settings, setSettings }: Props) {
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>Theme Settings</AccordionTrigger>
<AccordionTrigger>{t('setting.themeSettings')}</AccordionTrigger>
<AccordionContent className="space-y-4 flex flex-col">
<div className="flex items-center justify-between">
<Label htmlFor="app-theme">
<div>App Theme</div>
<div>{t('setting.appTheme')}</div>
</Label>
<div>
<button
@ -184,25 +183,25 @@ function SettingsDialog({ settings, setSettings }: Props) {
onClick={() => {
document
.querySelector("div.mt-2")
?.classList.toggle("dark"); // enable dark mode for sidebar
?.classList.toggle("dark");
document.body.classList.toggle("dark");
document
.querySelector('div[role="presentation"]')
?.classList.toggle("dark"); // enable dark mode for upload container
?.classList.toggle("dark");
}}
>
Toggle dark mode
{t('setting.toggleDarkMode')}
</button>
</div>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="editor-theme">
<div>
Code Editor Theme - requires page refresh to update
{t('setting.codeEditorTheme')}
</div>
</Label>
<div>
<Select // Use the custom Select component here
<Select
name="editor-theme"
value={settings.editorTheme}
onValueChange={(value) =>
@ -225,7 +224,7 @@ function SettingsDialog({ settings, setSettings }: Props) {
</div>
<DialogFooter>
<DialogClose>Save</DialogClose>
<DialogClose>{t('setting.save')}</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@ -12,6 +12,7 @@ import { Button } from "../ui/button";
import { Textarea } from "../ui/textarea";
import { useEffect, useRef } from "react";
import HistoryDisplay from "../history/HistoryDisplay";
import { useTranslation } from 'react-i18next';
interface SidebarProps {
showSelectAndEditFeature: boolean;
@ -26,6 +27,7 @@ function Sidebar({
regenerate,
cancelCodeGeneration,
}: SidebarProps) {
const { t } = useTranslation();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const {
@ -52,12 +54,8 @@ function Sidebar({
<div className="flex flex-col">
{/* Speed disclaimer for video mode */}
{inputMode === "video" && (
<div
className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700
p-2 text-xs mb-4 mt-1"
>
Code generation from videos can take 3-4 minutes. We do multiple
passes to get the best result. Please be patient.
<div className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-2 text-xs mb-4 mt-1">
{t('sidebar.videoGenerationWarning')}
</div>
)}
@ -73,7 +71,7 @@ function Sidebar({
onClick={cancelCodeGeneration}
className="w-full dark:text-white dark:bg-gray-700"
>
Cancel
{t('sidebar.cancel')}
</Button>
</div>
</div>
@ -84,7 +82,7 @@ function Sidebar({
<div className="grid w-full gap-2">
<Textarea
ref={textareaRef}
placeholder="Tell the AI what to change..."
placeholder={t('sidebar.updateInstructionPlaceholder')}
onChange={(e) => setUpdateInstruction(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
@ -95,7 +93,7 @@ function Sidebar({
/>
<div className="flex justify-between items-center gap-x-2">
<div className="font-500 text-xs text-slate-700 dark:text-white">
Include screenshot of current version?
{t('sidebar.includeScreenshot')}
</div>
<Switch
checked={shouldIncludeResultImage}
@ -107,7 +105,7 @@ function Sidebar({
onClick={() => doUpdate(updateInstruction)}
className="dark:text-white dark:bg-gray-700 update-btn"
>
Update <KeyboardShortcutBadge letter="enter" />
{t('sidebar.update')} <KeyboardShortcutBadge letter="enter" />
</Button>
</div>
<div className="flex items-center justify-end gap-x-2 mt-2">
@ -115,7 +113,7 @@ function Sidebar({
onClick={regenerate}
className="flex items-center gap-x-2 dark:text-white dark:bg-gray-700 regenerate-btn"
>
🔄 Regenerate
🔄 {t('sidebar.regenerate')}
</Button>
{showSelectAndEditFeature && <SelectAndEditModeToggleButton />}
</div>
@ -138,7 +136,7 @@ function Sidebar({
<img
className="w-[340px] border border-gray-200 rounded-md"
src={referenceImages[0]}
alt="Reference"
alt={t('sidebar.referenceImageAlt')}
/>
)}
{inputMode === "video" && (
@ -152,12 +150,12 @@ function Sidebar({
)}
</div>
<div className="text-gray-400 uppercase text-sm text-center mt-1">
{inputMode === "video" ? "Original Video" : "Original Screenshot"}
{inputMode === "video" ? t('sidebar.originalVideo') : t('sidebar.originalScreenshot')}
</div>
</div>
)}
<div className="bg-gray-400 px-4 py-2 rounded text-sm hidden">
<h2 className="text-lg mb-4 border-b border-gray-800">Console</h2>
<h2 className="text-lg mb-4 border-b border-gray-800">{t('sidebar.console')}</h2>
{executionConsole.map((line, index) => (
<div
key={index}