feat: add i18n and spanish and catalan translations

This commit is contained in:
Yoel Cabo 2024-10-07 03:26:28 +02:00
parent 6a9cb78fd6
commit ee0be3b605
14 changed files with 373 additions and 52 deletions

View File

@ -7,6 +7,7 @@ import type { ActionState } from '~/lib/runtime/action-runner';
import { workbenchStore } from '~/lib/stores/workbench';
import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings';
import { useTranslation } from 'react-i18next';
const highlighterOptions = {
langs: ['shell'],
@ -25,6 +26,7 @@ interface ArtifactProps {
}
export const Artifact = memo(({ messageId }: ArtifactProps) => {
const { t } = useTranslation();
const userToggledActions = useRef(false);
const [showActions, setShowActions] = useState(false);
@ -60,7 +62,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
>
<div className="px-5 p-3.5 w-full text-left">
<div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">{artifact?.title}</div>
<div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
<div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">{t('artifact.openWorkbench')}</div>
</div>
</button>
<div className="bg-bolt-elements-artifacts-borderColor w-[1px]" />
@ -130,6 +132,8 @@ const actionVariants = {
};
const ActionList = memo(({ actions }: ActionListProps) => {
const { t } = useTranslation();
return (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }}>
<ul className="list-none space-y-2.5">
@ -162,14 +166,14 @@ const ActionList = memo(({ actions }: ActionListProps) => {
</div>
{type === 'file' ? (
<div>
Create{' '}
{t('artifact.createFile')}{' '}
<code className="bg-bolt-elements-artifacts-inlineCode-background text-bolt-elements-artifacts-inlineCode-text px-1.5 py-1 rounded-md">
{action.filePath}
</code>
</div>
) : type === 'shell' ? (
<div className="flex items-center w-full min-h-[28px]">
<span className="flex-1">Run command</span>
<span className="flex-1">{t('artifact.runCommand')}</span>
</div>
) : null}
</div>

View File

@ -7,6 +7,7 @@ import { Workbench } from '~/components/workbench/Workbench.client';
import { classNames } from '~/utils/classNames';
import { Messages } from './Messages.client';
import { SendButton } from './SendButton.client';
import { useTranslation } from 'react-i18next';
import styles from './BaseChat.module.scss';
@ -27,14 +28,6 @@ interface BaseChatProps {
enhancePrompt?: () => void;
}
const EXAMPLE_PROMPTS = [
{ text: 'Build a todo app in React using Tailwind' },
{ text: 'Build a simple blog using Astro' },
{ text: 'Create a cookie consent form using Material UI' },
{ text: 'Make a space invaders game' },
{ text: 'How do I center a div?' },
];
const TEXTAREA_MIN_HEIGHT = 76;
export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
@ -57,7 +50,15 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
},
ref,
) => {
const { t } = useTranslation();
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
const EXAMPLE_PROMPTS = [
{ text: t('chat.examplePrompts.todoApp') },
{ text: t('chat.examplePrompts.astroBlog') },
{ text: t('chat.examplePrompts.cookieConsent') },
{ text: t('chat.examplePrompts.spaceInvaders') },
{ text: t('chat.examplePrompts.centerDiv') },
];
return (
<div
@ -74,10 +75,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
{!chatStarted && (
<div id="intro" className="mt-[26vh] max-w-chat mx-auto">
<h1 className="text-5xl text-center font-bold text-bolt-elements-textPrimary mb-2">
Where ideas begin
{t('title')}
</h1>
<p className="mb-4 text-center text-bolt-elements-textSecondary">
Bring ideas to life in seconds or get help on existing projects.
{t('subtitle')}
</p>
</div>
)}
@ -130,7 +131,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
minHeight: TEXTAREA_MIN_HEIGHT,
maxHeight: TEXTAREA_MAX_HEIGHT,
}}
placeholder="How can Bolt help you today?"
placeholder={t('messagePlaceholder')}
translate="no"
/>
<ClientOnly>
@ -152,7 +153,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
<div className="flex justify-between text-sm p-4 pt-2">
<div className="flex gap-1 items-center">
<IconButton
title="Enhance prompt"
title={t('chat.enhancePrompt')}
disabled={input.length === 0 || enhancingPrompt}
className={classNames({
'opacity-100!': enhancingPrompt,
@ -164,19 +165,19 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
{enhancingPrompt ? (
<>
<div className="i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress text-xl"></div>
<div className="ml-1.5">Enhancing prompt...</div>
<div className="ml-1.5">{t('chat.enhancingPrompt')}</div>
</>
) : (
<>
<div className="i-bolt:stars text-xl"></div>
{promptEnhanced && <div className="ml-1.5">Prompt enhanced</div>}
{promptEnhanced && <div className="ml-1.5">{t('chat.promptEnhanced')}</div>}
</>
)}
</IconButton>
</div>
{input.length > 3 ? (
<div className="text-xs text-bolt-elements-textTertiary">
Use <kbd className="kdb">Shift</kbd> + <kbd className="kdb">Return</kbd> for a new line
{t('chat.use')} <kbd className="kdb">{t('chat.shiftKey')}</kbd> + <kbd className="kdb">{t('chat.returnKey')}</kbd> {t('chat.forNewLine')}
</div>
) : null}
</div>
@ -210,4 +211,4 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
</div>
);
},
);
);

View File

@ -9,6 +9,7 @@ import { cubicEasingFn } from '~/utils/easings';
import { logger } from '~/utils/logger';
import { HistoryItem } from './HistoryItem';
import { binDates } from './date-binning';
import { useTranslation } from 'react-i18next';
const menuVariants = {
closed: {
@ -34,6 +35,7 @@ const menuVariants = {
type DialogContent = { type: 'delete'; item: ChatHistoryItem } | null;
export function Menu() {
const { t } = useTranslation();
const menuRef = useRef<HTMLDivElement>(null);
const [list, setList] = useState<ChatHistoryItem[]>([]);
const [open, setOpen] = useState(false);
@ -115,12 +117,12 @@ export function Menu() {
className="flex gap-2 items-center bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
>
<span className="inline-block i-bolt:chat scale-110" />
Start new chat
{t('startNewChat')}
</a>
</div>
<div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">Your Chats</div>
<div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">{t('yourChats')}</div>
<div className="flex-1 overflow-scroll pl-4 pr-5 pb-5">
{list.length === 0 && <div className="pl-2 text-bolt-elements-textTertiary">No previous conversations</div>}
{list.length === 0 && <div className="pl-2 text-bolt-elements-textTertiary">{t('noPreviousConversations')}</div>}
<DialogRoot open={dialogContent !== null}>
{binDates(list).map(({ category, items }) => (
<div key={category} className="mt-4 first:mt-0 space-y-1">
@ -139,14 +141,14 @@ export function Menu() {
<DialogDescription asChild>
<div>
<p>
You are about to delete <strong>{dialogContent.item.description}</strong>.
{t('aboutToDelete')} <strong>{dialogContent.item.description}</strong>.
</p>
<p className="mt-1">Are you sure you want to delete this chat?</p>
<p className="mt-1">{t('deleteChatConfirmation')}</p>
</div>
</DialogDescription>
<div className="px-5 pb-4 bg-bolt-elements-background-depth-2 flex gap-2 justify-end">
<DialogButton type="secondary" onClick={closeDialog}>
Cancel
{t('cancel')}
</DialogButton>
<DialogButton
type="danger"
@ -155,7 +157,7 @@ export function Menu() {
closeDialog();
}}
>
Delete
{t('delete')}
</DialogButton>
</div>
</>
@ -167,7 +169,7 @@ export function Menu() {
<a href="/logout">
<IconButton className="p-1.5 gap-1.5">
<>
Logout <span className="i-ph:sign-out text-lg" />
{t('logout')} <span className="i-ph:sign-out text-lg" />
</>
</IconButton>
</a>

View File

@ -23,6 +23,7 @@ import { isMobile } from '~/utils/mobile';
import { FileBreadcrumb } from './FileBreadcrumb';
import { FileTree } from './FileTree';
import { Terminal, type TerminalRef } from './terminal/Terminal';
import { useTranslation } from 'react-i18next';
interface EditorPanelProps {
files?: FileMap;
@ -80,6 +81,8 @@ export const EditorPanel = memo(
return editorDocument !== undefined && unsavedFiles?.has(editorDocument.filePath);
}, [editorDocument, unsavedFiles]);
const { t } = useTranslation();
useEffect(() => {
const unsubscribeFromEventEmitter = shortcutEventEmitter.on('toggleTerminal', () => {
terminalToggledByShortcut.current = true;
@ -130,7 +133,7 @@ export const EditorPanel = memo(
<div className="flex flex-col border-r border-bolt-elements-borderColor h-full">
<PanelHeader>
<div className="i-ph:tree-structure-duotone shrink-0" />
Files
{t('workbench.files')}
</PanelHeader>
<FileTree
className="h-full"
@ -153,11 +156,11 @@ export const EditorPanel = memo(
<div className="flex gap-1 ml-auto -mr-1.5">
<PanelHeaderButton onClick={onFileSave}>
<div className="i-ph:floppy-disk-duotone" />
Save
{t('workbench.save')}
</PanelHeaderButton>
<PanelHeaderButton onClick={onFileReset}>
<div className="i-ph:clock-counter-clockwise-duotone" />
Reset
{t('workbench.reset')}
</PanelHeaderButton>
</div>
)}
@ -216,7 +219,7 @@ export const EditorPanel = memo(
onClick={() => setActiveTerminal(index)}
>
<div className="i-ph:terminal-window-duotone text-lg" />
Terminal {terminalCount > 1 && index + 1}
{t('Terminal')} {terminalCount > 1 && index + 1} {/* Translate "Terminal" */}
</button>
);
})}
@ -224,7 +227,7 @@ export const EditorPanel = memo(
<IconButton
className="ml-auto"
icon="i-ph:caret-down"
title="Close"
title={t('workbench.closeWorkbench')}
size="md"
onClick={() => workbenchStore.toggleTerminal(false)}
/>
@ -253,4 +256,4 @@ export const EditorPanel = memo(
</PanelGroup>
);
},
);
);

View File

@ -3,6 +3,7 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { IconButton } from '~/components/ui/IconButton';
import { workbenchStore } from '~/lib/stores/workbench';
import { PortDropdown } from './PortDropdown';
import { useTranslation } from 'react-i18next';
export const Preview = memo(() => {
const iframeRef = useRef<HTMLIFrameElement>(null);
@ -71,6 +72,8 @@ export const Preview = memo(() => {
}
};
const { t } = useTranslation();
return (
<div className="w-full h-full flex flex-col">
{isPortDropdownOpen && (
@ -116,7 +119,7 @@ export const Preview = memo(() => {
{activePreview ? (
<iframe ref={iframeRef} className="border-none w-full h-full bg-white" src={iframeUrl} />
) : (
<div className="flex w-full h-full justify-center items-center bg-white">No preview available</div>
<div className="flex w-full h-full justify-center items-center bg-white">{t('preview.noPreview')}</div>
)}
</div>
</div>

View File

@ -3,6 +3,7 @@ import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
import { computed } from 'nanostores';
import { memo, useCallback, useEffect } from 'react';
import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next';
import {
type OnChangeCallback as OnEditorChange,
type OnScrollCallback as OnEditorScroll,
@ -24,17 +25,6 @@ interface WorkspaceProps {
const viewTransition = { ease: cubicEasingFn };
const sliderOptions: SliderOptions<WorkbenchViewType> = {
left: {
value: 'code',
text: 'Code',
},
right: {
value: 'preview',
text: 'Preview',
},
};
const workbenchVariants = {
closed: {
width: 0,
@ -54,6 +44,17 @@ const workbenchVariants = {
export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) => {
renderLogger.trace('Workbench');
const { t } = useTranslation();
const sliderOptions: SliderOptions<WorkbenchViewType> = {
left: {
value: 'code',
text: t('workbench.code'),
},
right: {
value: 'preview',
text: t('workbench.preview'),
},
};
const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
const showWorkbench = useStore(workbenchStore.showWorkbench);
@ -129,7 +130,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
}}
>
<div className="i-ph:terminal" />
Toggle Terminal
{t('workbench.toggleTerminal')}
</PanelHeaderButton>
)}
<IconButton
@ -139,6 +140,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
onClick={() => {
workbenchStore.showWorkbench.set(false);
}}
title={t('workbench.closeWorkbench')}
/>
</div>
<div className="relative flex-1 overflow-hidden">

26
app/i18n.ts Normal file
View File

@ -0,0 +1,26 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
export function initI18n() {
if (!i18n.isInitialized) {
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
supportedLngs: ['ca', 'es', 'en'],
load: 'languageOnly',
debug: true,
interpolation: {
escapeValue: false,
},
});
}
return i18n;
}
export default i18n;

View File

@ -6,6 +6,8 @@ import { themeStore } from './lib/stores/theme';
import { stripIndents } from './utils/stripIndent';
import { createHead } from 'remix-island';
import { useEffect } from 'react';
import { I18nextProvider } from 'react-i18next';
import { initI18n } from './i18n';
import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
import globalStyles from './styles/index.scss?url';
@ -64,17 +66,20 @@ export const Head = createHead(() => (
export function Layout({ children }: { children: React.ReactNode }) {
const theme = useStore(themeStore);
const i18n = initI18n();
useEffect(() => {
document.querySelector('html')?.setAttribute('data-theme', theme);
}, [theme]);
return (
<>
{children}
<ScrollRestoration />
<Scripts />
</>
<I18nextProvider i18n={i18n}>
<>
{children}
<ScrollRestoration />
<Scripts />
</>
</I18nextProvider>
);
}

View File

@ -22,6 +22,7 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
I want you to improve the user prompt that is wrapped in \`<original_prompt>\` tags.
IMPORTANT: Only respond with the improved prompt and nothing else!
Keep the original language of the prompt.
<original_prompt>
${message}

View File

@ -58,6 +58,9 @@
"date-fns": "^3.6.0",
"diff": "^5.2.0",
"framer-motion": "^11.2.12",
"i18next": "^23.15.2",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.2",
"isbot": "^4.1.0",
"istextorbinary": "^9.5.0",
"jose": "^5.6.3",
@ -65,15 +68,16 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.2",
"react-markdown": "^9.0.1",
"react-resizable-panels": "^2.0.20",
"react-toastify": "^10.0.5",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.0",
"remix-island": "^0.2.0",
"remix-utils": "^7.6.0",
"shiki": "^1.9.1",
"remix-island": "^0.2.0",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {

View File

@ -116,6 +116,15 @@ importers:
framer-motion:
specifier: ^11.2.12
version: 11.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
i18next:
specifier: ^23.15.2
version: 23.15.2
i18next-browser-languagedetector:
specifier: ^8.0.0
version: 8.0.0
i18next-http-backend:
specifier: ^2.6.2
version: 2.6.2
isbot:
specifier: ^4.1.0
version: 4.4.0
@ -137,6 +146,9 @@ importers:
react-hotkeys-hook:
specifier: ^4.5.0
version: 4.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-i18next:
specifier: ^15.0.2
version: 15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-markdown:
specifier: ^9.0.1
version: 9.0.1(@types/react@18.3.3)(react@18.3.1)
@ -455,6 +467,10 @@ packages:
resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==}
engines: {node: '>=6.9.0'}
'@babel/runtime@7.25.7':
resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==}
engines: {node: '>=6.9.0'}
'@babel/template@7.24.7':
resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
engines: {node: '>=6.9.0'}
@ -2392,6 +2408,9 @@ packages:
crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
cross-fetch@4.0.0:
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@ -3046,6 +3065,9 @@ packages:
resolution: {integrity: sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
html-parse-stringify@3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
html-url-attributes@3.0.0:
resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==}
@ -3067,6 +3089,15 @@ packages:
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
engines: {node: '>=16.17.0'}
i18next-browser-languagedetector@8.0.0:
resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==}
i18next-http-backend@2.6.2:
resolution: {integrity: sha512-Hp/kd8/VuoxIHmxsknJXjkTYYHzivAyAF15pzliKzk2TiXC25rZCEerb1pUFoxz4IVrG3fCvQSY51/Lu4ECV4A==}
i18next@23.15.2:
resolution: {integrity: sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==}
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@ -3817,6 +3848,15 @@ packages:
node-fetch-native@1.6.4:
resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==}
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -4214,6 +4254,19 @@ packages:
react: '>=16.8.1'
react-dom: '>=16.8.1'
react-i18next@15.0.2:
resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==}
peerDependencies:
i18next: '>= 23.2.3'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
@ -4738,6 +4791,9 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@ -5061,6 +5117,10 @@ packages:
vm-browserify@1.1.2:
resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
vue@3.4.30:
resolution: {integrity: sha512-NcxtKCwkdf1zPsr7Y8+QlDBCGqxvjLXF2EX+yi76rV5rrz90Y6gK1cq0olIhdWGgrlhs9ElHuhi9t3+W5sG5Xw==}
peerDependencies:
@ -5085,6 +5145,12 @@ packages:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-typed-array@1.1.15:
resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
engines: {node: '>= 0.4'}
@ -5474,6 +5540,10 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
'@babel/runtime@7.25.7':
dependencies:
regenerator-runtime: 0.14.1
'@babel/template@7.24.7':
dependencies:
'@babel/code-frame': 7.24.7
@ -7616,6 +7686,12 @@ snapshots:
crelt@1.0.6: {}
cross-fetch@4.0.0:
dependencies:
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
@ -8442,6 +8518,10 @@ snapshots:
dependencies:
lru-cache: 7.18.3
html-parse-stringify@3.0.1:
dependencies:
void-elements: 3.1.0
html-url-attributes@3.0.0: {}
html-void-elements@3.0.0: {}
@ -8460,6 +8540,20 @@ snapshots:
human-signals@5.0.0: {}
i18next-browser-languagedetector@8.0.0:
dependencies:
'@babel/runtime': 7.24.7
i18next-http-backend@2.6.2:
dependencies:
cross-fetch: 4.0.0
transitivePeerDependencies:
- encoding
i18next@23.15.2:
dependencies:
'@babel/runtime': 7.24.7
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@ -9533,6 +9627,10 @@ snapshots:
node-fetch-native@1.6.4: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
@ -9965,6 +10063,15 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-i18next@15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.25.7
html-parse-stringify: 3.0.1
i18next: 23.15.2
react: 18.3.1
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
react-is@18.3.1: {}
react-markdown@9.0.1(@types/react@18.3.3)(react@18.3.1):
@ -10563,6 +10670,8 @@ snapshots:
totalist@3.0.1: {}
tr46@0.0.3: {}
trim-lines@3.0.1: {}
trough@2.2.0: {}
@ -10951,6 +11060,8 @@ snapshots:
vm-browserify@1.1.2: {}
void-elements@3.1.0: {}
vue@3.4.30(typescript@5.5.2):
dependencies:
'@vue/compiler-dom': 3.4.30
@ -10977,6 +11088,13 @@ snapshots:
web-streams-polyfill@3.3.3: {}
webidl-conversions@3.0.1: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
which-typed-array@1.1.15:
dependencies:
available-typed-arrays: 1.0.7

View File

@ -0,0 +1,51 @@
{
"title": "Què vols construir?",
"subtitle": "Demana, executa, edita i desplega aplicacions web full-stack.",
"clearHistory": "Esborrar historial",
"sendMessage": "Enviar",
"messagePlaceholder": "Com pot ajudar-te el Bolt avui?",
"startNewChat": "Inicia un nou xat",
"yourChats": "Els teus xats",
"noPreviousConversations": "No hi ha converses prèvies",
"logout": "Tancar sessió",
"deleteChat": "Eliminar xat?",
"deleteChatConfirmation": "Estàs segur que vols eliminar aquest xat?",
"cancel": "Cancel·lar",
"delete": "Eliminar",
"aboutToDelete": "Estàs a punt d'eliminar",
"artifact": {
"openWorkbench": "Feu clic per obrir l'espai de desenvolupament",
"createFile": "Crea",
"runCommand": "Executa comanda"
},
"workbench": {
"code": "Codi",
"preview": "Vista prèvia",
"toggleTerminal": "Mostra/Oculta terminal",
"closeWorkbench": "Tanca l'espai de desenvolupament",
"files": "Fitxers",
"save": "Guardar",
"reset": "Reiniciar"
},
"preview": {
"noPreview": "Vista prèvia no disponible",
"previewError": "S'ha produït un error en generar la vista prèvia",
"retry": "Tornem-hi"
},
"chat": {
"enhancePrompt": "Millora la pregunta",
"enhancingPrompt": "Millorant la pregunta...",
"promptEnhanced": "Pregunta millorada",
"use": "Prem",
"shiftKey": "Shift",
"returnKey": "Enter",
"forNewLine": "per a una nova línia",
"examplePrompts": {
"todoApp": "Crea una aplicació de tasques en React utilitzant Tailwind",
"astroBlog": "Crea un bloc senzill utilitzant Astro",
"cookieConsent": "Crea un formulari de consentiment per a cookies utilitzant Material UI",
"spaceInvaders": "Crea un joc d'Space Invaders",
"centerDiv": "Com centro un div?"
}
}
}

View File

@ -0,0 +1,51 @@
{
"title": "What do you want to build?",
"subtitle": "Prompt, run, edit, and deploy full-stack web apps.",
"clearHistory": "Clear History",
"sendMessage": "Send",
"messagePlaceholder": "How can Bolt help you today?",
"startNewChat": "Start new chat",
"yourChats": "Your Chats",
"noPreviousConversations": "No previous conversations",
"logout": "Logout",
"deleteChat": "Delete Chat?",
"deleteChatConfirmation": "Are you sure you want to delete this chat?",
"cancel": "Cancel",
"delete": "Delete",
"aboutToDelete": "You are about to delete",
"artifact": {
"openWorkbench": "Click to open Workbench",
"createFile": "Create",
"runCommand": "Run command"
},
"workbench": {
"code": "Code",
"preview": "Preview",
"toggleTerminal": "Toggle Terminal",
"closeWorkbench": "Close Workbench",
"save": "Save",
"reset": "Reset",
"files": "Files"
},
"preview": {
"noPreview": "No preview available",
"previewError": "An error occurred while generating the preview",
"retry": "Retry"
},
"chat": {
"enhancePrompt": "Enhance prompt",
"enhancingPrompt": "Enhancing prompt...",
"promptEnhanced": "Prompt enhanced",
"use": "Use",
"shiftKey": "Shift",
"returnKey": "Enter",
"forNewLine": "for a new line",
"examplePrompts": {
"todoApp": "Build a todo app in React using Tailwind",
"astroBlog": "Build a simple blog using Astro",
"cookieConsent": "Create a cookie consent form using Material UI",
"spaceInvaders": "Make a space invaders game",
"centerDiv": "How do I center a div?"
}
}
}

View File

@ -0,0 +1,50 @@
{
"title": "¿Qué quieres construir?",
"subtitle": "Genera, ejecuta, edita y despliega aplicaciones web full-stack.",
"clearHistory": "Borrar historial",
"sendMessage": "Enviar",
"messagePlaceholder": "¿Cómo puede ayudarte Bolt hoy?",
"startNewChat": "Iniciar nuevo chat",
"yourChats": "Tus chats",
"noPreviousConversations": "No hay conversaciones previas",
"logout": "Cerrar sesión",
"deleteChat": "¿Eliminar chat?",
"deleteChatConfirmation": "¿Estás seguro de que quieres eliminar este chat?",
"cancel": "Cancelar",
"delete": "Eliminar",
"aboutToDelete": "Estás a punto de eliminar",
"artifact": {
"openWorkbench": "Haz clic para abrir el espacio de desarrollo",
"createFile": "Crear",
"runCommand": "Ejecutar comando"
},
"workbench": {
"code": "Código",
"files": "Archivos",
"save": "Guardar",
"reset": "Reiniciar",
"toggleTerminal": "Mostrar/Ocultar terminal",
"closeWorkbench": "Cerrar espacio de desarrollo"
},
"preview": {
"noPreview": "Vista previa no disponible",
"previewError": "Se produjo un error al generar la vista previa",
"retry": "Reintentar"
},
"chat": {
"enhancePrompt": "Mejorar la pregunta",
"enhancingPrompt": "Mejorando la pregunta...",
"promptEnhanced": "Pregunta mejorada",
"use": "Usa",
"shiftKey": "Shift",
"returnKey": "Enter",
"forNewLine": "para una nueva línea",
"examplePrompts": {
"todoApp": "Crea una aplicació de tareas en React utilizando Tailwind",
"astroBlog": "Crea un blog sencillo utilizando Astro",
"cookieConsent": "Crea un formulario de consentimiento para cookies utilizando Material UI",
"spaceInvaders": "Crea un juego como Space Invaders",
"centerDiv": "¿Cómo centro un div?"
}
}
}