mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
feat: add i18n and spanish and catalan translations
This commit is contained in:
parent
6a9cb78fd6
commit
ee0be3b605
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
},
|
||||
);
|
||||
);
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
},
|
||||
);
|
||||
);
|
||||
@ -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>
|
||||
|
||||
@ -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
26
app/i18n.ts
Normal 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;
|
||||
15
app/root.tsx
15
app/root.tsx
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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": {
|
||||
|
||||
118
pnpm-lock.yaml
118
pnpm-lock.yaml
@ -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
|
||||
|
||||
51
public/locales/ca/translation.json
Normal file
51
public/locales/ca/translation.json
Normal 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?"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
public/locales/en/translation.json
Normal file
51
public/locales/en/translation.json
Normal 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?"
|
||||
}
|
||||
}
|
||||
}
|
||||
50
public/locales/es/translation.json
Normal file
50
public/locales/es/translation.json
Normal 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?"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user