Skip to content

Commit

Permalink
feat: add i18n and spanish and catalan translations
Browse files Browse the repository at this point in the history
  • Loading branch information
yoelcabo committed Oct 7, 2024
1 parent 6a9cb78 commit 94822a1
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 52 deletions.
10 changes: 7 additions & 3 deletions app/components/chat/Artifact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -25,6 +26,7 @@ interface ArtifactProps {
}

export const Artifact = memo(({ messageId }: ArtifactProps) => {
const { t } = useTranslation();
const userToggledActions = useRef(false);
const [showActions, setShowActions] = useState(false);

Expand Down Expand Up @@ -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]" />
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down
33 changes: 17 additions & 16 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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>(
Expand All @@ -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
Expand All @@ -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>
)}
Expand Down Expand Up @@ -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>
Expand All @@ -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,
Expand All @@ -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>
Expand Down Expand Up @@ -210,4 +211,4 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
</div>
);
},
);
);
18 changes: 10 additions & 8 deletions app/components/sidebar/Menu.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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);
Expand Down Expand Up @@ -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">
Expand All @@ -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"
Expand All @@ -155,7 +157,7 @@ export function Menu() {
closeDialog();
}}
>
Delete
{t('delete')}
</DialogButton>
</div>
</>
Expand All @@ -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>
Expand Down
15 changes: 9 additions & 6 deletions app/components/workbench/EditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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"
Expand All @@ -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>
)}
Expand Down Expand Up @@ -216,15 +219,15 @@ 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>
);
})}
{terminalCount < MAX_TERMINALS && <IconButton icon="i-ph:plus" size="md" onClick={addTerminal} />}
<IconButton
className="ml-auto"
icon="i-ph:caret-down"
title="Close"
title={t('workbench.closeWorkbench')}
size="md"
onClick={() => workbenchStore.toggleTerminal(false)}
/>
Expand Down Expand Up @@ -253,4 +256,4 @@ export const EditorPanel = memo(
</PanelGroup>
);
},
);
);
5 changes: 4 additions & 1 deletion app/components/workbench/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -71,6 +72,8 @@ export const Preview = memo(() => {
}
};

const { t } = useTranslation();

return (
<div className="w-full h-full flex flex-col">
{isPortDropdownOpen && (
Expand Down Expand Up @@ -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>
Expand Down
26 changes: 14 additions & 12 deletions app/components/workbench/Workbench.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -129,7 +130,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
}}
>
<div className="i-ph:terminal" />
Toggle Terminal
{t('workbench.toggleTerminal')}
</PanelHeaderButton>
)}
<IconButton
Expand All @@ -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">
Expand Down
21 changes: 21 additions & 0 deletions app/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';

i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
supportedLngs: ['ca', 'es', 'en'],
load: 'languageOnly',
debug: true,
interpolation: {
escapeValue: false,
},
});

export default i18n;
Loading

0 comments on commit 94822a1

Please sign in to comment.