feat(desktop): add i18n infrastructure with react-intl (#8105)
This commit is contained in:
@@ -5,14 +5,36 @@ import { ResponseStylesSection } from '../response_styles/ResponseStylesSection'
|
||||
import { GoosehintsSection } from './GoosehintsSection';
|
||||
import { SpellcheckToggle } from './SpellcheckToggle';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
||||
import { defineMessages, useIntl } from '../../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
modeTitle: {
|
||||
id: 'chatSettings.modeTitle',
|
||||
defaultMessage: 'Mode',
|
||||
},
|
||||
modeDescription: {
|
||||
id: 'chatSettings.modeDescription',
|
||||
defaultMessage: 'Configure how Goose interacts with tools and extensions',
|
||||
},
|
||||
responseStylesTitle: {
|
||||
id: 'chatSettings.responseStylesTitle',
|
||||
defaultMessage: 'Response Styles',
|
||||
},
|
||||
responseStylesDescription: {
|
||||
id: 'chatSettings.responseStylesDescription',
|
||||
defaultMessage: 'Choose how Goose should format and style its responses',
|
||||
},
|
||||
});
|
||||
|
||||
export default function ChatSettingsSection({ sessionId }: { sessionId?: string }) {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pr-4 pb-8 mt-1">
|
||||
<Card className="pb-2 rounded-lg">
|
||||
<CardHeader className="pb-0">
|
||||
<CardTitle className="">Mode</CardTitle>
|
||||
<CardDescription>Configure how Goose interacts with tools and extensions</CardDescription>
|
||||
<CardTitle className="">{intl.formatMessage(i18n.modeTitle)}</CardTitle>
|
||||
<CardDescription>{intl.formatMessage(i18n.modeDescription)}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2">
|
||||
<ModeSection sessionId={sessionId} />
|
||||
@@ -34,8 +56,8 @@ export default function ChatSettingsSection({ sessionId }: { sessionId?: string
|
||||
|
||||
<Card className="pb-2 rounded-lg">
|
||||
<CardHeader className="pb-0">
|
||||
<CardTitle className="">Response Styles</CardTitle>
|
||||
<CardDescription>Choose how Goose should format and style its responses</CardDescription>
|
||||
<CardTitle className="">{intl.formatMessage(i18n.responseStylesTitle)}</CardTitle>
|
||||
<CardDescription>{intl.formatMessage(i18n.responseStylesDescription)}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2">
|
||||
<ResponseStylesSection />
|
||||
|
||||
@@ -10,51 +10,143 @@ import {
|
||||
DialogTitle,
|
||||
} from '../../ui/dialog';
|
||||
import { errorMessage } from '../../../utils/conversionUtils';
|
||||
import { defineMessages, useIntl } from '../../../i18n';
|
||||
|
||||
const HelpText = () => (
|
||||
<div className="text-sm flex-col space-y-4 text-text-secondary">
|
||||
<p>
|
||||
.goosehints is a text file used to provide additional context about your project and improve
|
||||
the communication with Goose.
|
||||
</p>
|
||||
<p>
|
||||
Please make sure <span className="font-bold">Developer</span> extension is enabled in the
|
||||
extensions page. This extension is required to use .goosehints. You'll need to restart your
|
||||
session for .goosehints updates to take effect.
|
||||
</p>
|
||||
<p>
|
||||
See{' '}
|
||||
<Button
|
||||
variant="link"
|
||||
className="text-blue-500 hover:text-blue-600 p-0 h-auto"
|
||||
onClick={() =>
|
||||
window.open('https://block.github.io/goose/docs/guides/using-goosehints/', '_blank')
|
||||
}
|
||||
>
|
||||
using .goosehints
|
||||
</Button>{' '}
|
||||
for more information.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
const i18n = defineMessages({
|
||||
dialogTitle: {
|
||||
id: 'goosehintsModal.dialogTitle',
|
||||
defaultMessage: 'Configure Project Hints (.goosehints)',
|
||||
},
|
||||
dialogDescription: {
|
||||
id: 'goosehintsModal.dialogDescription',
|
||||
defaultMessage:
|
||||
'Provide additional context about your project to improve communication with Goose',
|
||||
},
|
||||
helpText1: {
|
||||
id: 'goosehintsModal.helpText1',
|
||||
defaultMessage:
|
||||
'.goosehints is a text file used to provide additional context about your project and improve the communication with Goose.',
|
||||
},
|
||||
helpText2: {
|
||||
id: 'goosehintsModal.helpText2',
|
||||
defaultMessage:
|
||||
"Please make sure {bold} extension is enabled in the extensions page. This extension is required to use .goosehints. You'll need to restart your session for .goosehints updates to take effect.",
|
||||
},
|
||||
helpText3: {
|
||||
id: 'goosehintsModal.helpText3',
|
||||
defaultMessage: 'See {link} for more information.',
|
||||
},
|
||||
helpTextLink: {
|
||||
id: 'goosehintsModal.helpTextLink',
|
||||
defaultMessage: 'using .goosehints',
|
||||
},
|
||||
errorReading: {
|
||||
id: 'goosehintsModal.errorReading',
|
||||
defaultMessage: 'Error reading .goosehints file: {error}',
|
||||
},
|
||||
fileFound: {
|
||||
id: 'goosehintsModal.fileFound',
|
||||
defaultMessage: '.goosehints file found at: {filePath}',
|
||||
},
|
||||
fileCreating: {
|
||||
id: 'goosehintsModal.fileCreating',
|
||||
defaultMessage: 'Creating new .goosehints file at: {filePath}',
|
||||
},
|
||||
placeholder: {
|
||||
id: 'goosehintsModal.placeholder',
|
||||
defaultMessage: 'Enter project hints here...',
|
||||
},
|
||||
savedSuccessfully: {
|
||||
id: 'goosehintsModal.savedSuccessfully',
|
||||
defaultMessage: 'Saved successfully',
|
||||
},
|
||||
close: {
|
||||
id: 'goosehintsModal.close',
|
||||
defaultMessage: 'Close',
|
||||
},
|
||||
saving: {
|
||||
id: 'goosehintsModal.saving',
|
||||
defaultMessage: 'Saving...',
|
||||
},
|
||||
save: {
|
||||
id: 'goosehintsModal.save',
|
||||
defaultMessage: 'Save',
|
||||
},
|
||||
failedToAccess: {
|
||||
id: 'goosehintsModal.failedToAccess',
|
||||
defaultMessage: 'Failed to access .goosehints file',
|
||||
},
|
||||
failedToSave: {
|
||||
id: 'goosehintsModal.failedToSave',
|
||||
defaultMessage: 'Failed to save .goosehints file',
|
||||
},
|
||||
developer: {
|
||||
id: 'goosehintsModal.developer',
|
||||
defaultMessage: 'Developer',
|
||||
},
|
||||
});
|
||||
|
||||
const ErrorDisplay = ({ error }: { error: Error }) => (
|
||||
<div className="text-sm text-text-secondary">
|
||||
<div className="text-red-600">Error reading .goosehints file: {errorMessage(error)}</div>
|
||||
</div>
|
||||
);
|
||||
const HelpText = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const FileInfo = ({ filePath, found }: { filePath: string; found: boolean }) => (
|
||||
<div className="text-sm font-medium mb-2">
|
||||
{found ? (
|
||||
<div className="text-green-600">
|
||||
<Check className="w-4 h-4 inline-block" /> .goosehints file found at: {filePath}
|
||||
return (
|
||||
<div className="text-sm flex-col space-y-4 text-text-secondary">
|
||||
<p>{intl.formatMessage(i18n.helpText1)}</p>
|
||||
<p>
|
||||
{intl.formatMessage(i18n.helpText2, {
|
||||
bold: <span className="font-bold">{intl.formatMessage(i18n.developer)}</span>,
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{intl.formatMessage(i18n.helpText3, {
|
||||
link: (
|
||||
<Button
|
||||
variant="link"
|
||||
className="text-blue-500 hover:text-blue-600 p-0 h-auto"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
'https://block.github.io/goose/docs/guides/using-goosehints/',
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
>
|
||||
{intl.formatMessage(i18n.helpTextLink)}
|
||||
</Button>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorDisplay = ({ error }: { error: Error }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div className="text-sm text-text-secondary">
|
||||
<div className="text-red-600">
|
||||
{intl.formatMessage(i18n.errorReading, { error: errorMessage(error) })}
|
||||
</div>
|
||||
) : (
|
||||
<div>Creating new .goosehints file at: {filePath}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FileInfo = ({ filePath, found }: { filePath: string; found: boolean }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div className="text-sm font-medium mb-2">
|
||||
{found ? (
|
||||
<div className="text-green-600">
|
||||
<Check className="w-4 h-4 inline-block" />{' '}
|
||||
{intl.formatMessage(i18n.fileFound, { filePath })}
|
||||
</div>
|
||||
) : (
|
||||
<div>{intl.formatMessage(i18n.fileCreating, { filePath })}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getGoosehintsFile = async (filePath: string) => await window.electron.readFile(filePath);
|
||||
|
||||
@@ -64,6 +156,7 @@ interface GoosehintsModalProps {
|
||||
}
|
||||
|
||||
export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: GoosehintsModalProps) => {
|
||||
const intl = useIntl();
|
||||
const goosehintsFilePath = `${directory}/.goosehints`;
|
||||
const [goosehintsFile, setGoosehintsFile] = useState<string>('');
|
||||
const [goosehintsFileFound, setGoosehintsFileFound] = useState<boolean>(false);
|
||||
@@ -80,11 +173,11 @@ export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: Goosehi
|
||||
setGoosehintsFileReadError(found && error ? error : '');
|
||||
} catch (error) {
|
||||
console.error('Error fetching .goosehints file:', error);
|
||||
setGoosehintsFileReadError('Failed to access .goosehints file');
|
||||
setGoosehintsFileReadError(intl.formatMessage(i18n.failedToAccess));
|
||||
}
|
||||
};
|
||||
if (directory) fetchGoosehintsFile();
|
||||
}, [directory, goosehintsFilePath]);
|
||||
}, [directory, goosehintsFilePath, intl]);
|
||||
|
||||
const writeFile = async () => {
|
||||
setIsSaving(true);
|
||||
@@ -96,7 +189,7 @@ export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: Goosehi
|
||||
setTimeout(() => setSaveSuccess(false), 3000);
|
||||
} catch (error) {
|
||||
console.error('Error writing .goosehints file:', error);
|
||||
setGoosehintsFileReadError('Failed to save .goosehints file');
|
||||
setGoosehintsFileReadError(intl.formatMessage(i18n.failedToSave));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -106,10 +199,8 @@ export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: Goosehi
|
||||
<Dialog open={true} onOpenChange={(open) => setIsGoosehintsModalOpen(open)}>
|
||||
<DialogContent className="w-[80vw] max-w-[80vw] sm:max-w-[80vw] max-h-[90vh] flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Configure Project Hints (.goosehints)</DialogTitle>
|
||||
<DialogDescription>
|
||||
Provide additional context about your project to improve communication with Goose
|
||||
</DialogDescription>
|
||||
<DialogTitle>{intl.formatMessage(i18n.dialogTitle)}</DialogTitle>
|
||||
<DialogDescription>{intl.formatMessage(i18n.dialogDescription)}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto space-y-4 pt-2 pb-4">
|
||||
@@ -125,7 +216,7 @@ export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: Goosehi
|
||||
value={goosehintsFile}
|
||||
className="w-full h-80 border rounded-md p-2 text-sm resize-none bg-background-primary text-text-primary border-border-primary focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
onChange={(event) => setGoosehintsFile(event.target.value)}
|
||||
placeholder="Enter project hints here..."
|
||||
placeholder={intl.formatMessage(i18n.placeholder)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -136,14 +227,14 @@ export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: Goosehi
|
||||
{saveSuccess && (
|
||||
<span className="text-green-600 text-sm flex items-center gap-1 mr-auto">
|
||||
<Check className="w-4 h-4" />
|
||||
Saved successfully
|
||||
{intl.formatMessage(i18n.savedSuccessfully)}
|
||||
</span>
|
||||
)}
|
||||
<Button variant="outline" onClick={() => setIsGoosehintsModalOpen(false)}>
|
||||
Close
|
||||
{intl.formatMessage(i18n.close)}
|
||||
</Button>
|
||||
<Button onClick={writeFile} disabled={isSaving}>
|
||||
{isSaving ? 'Saving...' : 'Save'}
|
||||
{isSaving ? intl.formatMessage(i18n.saving) : intl.formatMessage(i18n.save)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -2,8 +2,26 @@ import { useState } from 'react';
|
||||
import { Button } from '../../ui/button';
|
||||
import { FolderKey } from 'lucide-react';
|
||||
import { GoosehintsModal } from './GoosehintsModal';
|
||||
import { defineMessages, useIntl } from '../../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
title: {
|
||||
id: 'goosehintsSection.title',
|
||||
defaultMessage: 'Project Hints (.goosehints)',
|
||||
},
|
||||
description: {
|
||||
id: 'goosehintsSection.description',
|
||||
defaultMessage:
|
||||
"Configure your project's .goosehints file to provide additional context to Goose",
|
||||
},
|
||||
configure: {
|
||||
id: 'goosehintsSection.configure',
|
||||
defaultMessage: 'Configure',
|
||||
},
|
||||
});
|
||||
|
||||
export const GoosehintsSection = () => {
|
||||
const intl = useIntl();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const directory = window.appConfig?.get('GOOSE_WORKING_DIR') as string;
|
||||
|
||||
@@ -11,9 +29,9 @@ export const GoosehintsSection = () => {
|
||||
<>
|
||||
<div className="flex items-center justify-between px-2 py-2">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-text-primary">Project Hints (.goosehints)</h3>
|
||||
<h3 className="text-text-primary">{intl.formatMessage(i18n.title)}</h3>
|
||||
<p className="text-xs text-text-secondary mt-[2px]">
|
||||
Configure your project's .goosehints file to provide additional context to Goose
|
||||
{intl.formatMessage(i18n.description)}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -23,7 +41,7 @@ export const GoosehintsSection = () => {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<FolderKey size={16} />
|
||||
Configure
|
||||
{intl.formatMessage(i18n.configure)}
|
||||
</Button>
|
||||
</div>
|
||||
{isModalOpen && (
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Switch } from '../../ui/switch';
|
||||
import { defineMessages, useIntl } from '../../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
title: {
|
||||
id: 'spellcheckToggle.title',
|
||||
defaultMessage: 'Enable Spellcheck',
|
||||
},
|
||||
description: {
|
||||
id: 'spellcheckToggle.description',
|
||||
defaultMessage: 'Check spelling in the chat input. Requires restart to take effect.',
|
||||
},
|
||||
});
|
||||
|
||||
export const SpellcheckToggle = () => {
|
||||
const intl = useIntl();
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -20,9 +33,9 @@ export const SpellcheckToggle = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-2 px-2 hover:bg-background-secondary rounded-lg transition-all">
|
||||
<div>
|
||||
<h3 className="text-text-primary">Enable Spellcheck</h3>
|
||||
<h3 className="text-text-primary">{intl.formatMessage(i18n.title)}</h3>
|
||||
<p className="text-xs text-text-secondary max-w-md mt-[2px]">
|
||||
Check spelling in the chat input. Requires restart to take effect.
|
||||
{intl.formatMessage(i18n.description)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
|
||||
Reference in New Issue
Block a user