feat(desktop): add i18n infrastructure with react-intl (#8105)
This commit is contained in:
@@ -6,6 +6,52 @@ import { exportApp, GooseApp, importApp, listApps } from '../../api';
|
||||
import { useChatContext } from '../../contexts/ChatContext';
|
||||
import { formatAppName } from '../../utils/conversionUtils';
|
||||
import { errorMessage } from '../../utils/conversionUtils';
|
||||
import { defineMessages, useIntl } from '../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
errorLoading: {
|
||||
id: 'appsView.errorLoading',
|
||||
defaultMessage: 'Error loading apps: {error}',
|
||||
},
|
||||
retry: {
|
||||
id: 'appsView.retry',
|
||||
defaultMessage: 'Retry',
|
||||
},
|
||||
title: {
|
||||
id: 'appsView.title',
|
||||
defaultMessage: 'Apps',
|
||||
},
|
||||
importApp: {
|
||||
id: 'appsView.importApp',
|
||||
defaultMessage: 'Import App',
|
||||
},
|
||||
description: {
|
||||
id: 'appsView.description',
|
||||
defaultMessage:
|
||||
'Applications from your MCP servers and Apps build by goose itself. You can ask it to create new apps through the chat interface and they will appear here.',
|
||||
},
|
||||
loading: {
|
||||
id: 'appsView.loading',
|
||||
defaultMessage: 'Loading apps...',
|
||||
},
|
||||
noAppsTitle: {
|
||||
id: 'appsView.noAppsTitle',
|
||||
defaultMessage: 'No apps available',
|
||||
},
|
||||
noAppsDescription: {
|
||||
id: 'appsView.noAppsDescription',
|
||||
defaultMessage:
|
||||
'Open a chat and ask goose for the app you want to have. It can build one for you and that will appear here. Or if somebody shared an app, you can import it using the button above.',
|
||||
},
|
||||
customApp: {
|
||||
id: 'appsView.customApp',
|
||||
defaultMessage: 'Custom app',
|
||||
},
|
||||
launch: {
|
||||
id: 'appsView.launch',
|
||||
defaultMessage: 'Launch',
|
||||
},
|
||||
});
|
||||
|
||||
const GridLayout = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
@@ -22,6 +68,7 @@ const GridLayout = ({ children }: { children: React.ReactNode }) => {
|
||||
};
|
||||
|
||||
export default function AppsView() {
|
||||
const intl = useIntl();
|
||||
const [apps, setApps] = useState<GooseApp[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -195,8 +242,8 @@ export default function AppsView() {
|
||||
return (
|
||||
<MainPanelLayout>
|
||||
<div className="flex flex-col items-center justify-center h-64 text-center">
|
||||
<p className="text-red-500 mb-4">Error loading apps: {error}</p>
|
||||
<Button onClick={loadApps}>Retry</Button>
|
||||
<p className="text-red-500 mb-4">{intl.formatMessage(i18n.errorLoading, { error })}</p>
|
||||
<Button onClick={loadApps}>{intl.formatMessage(i18n.retry)}</Button>
|
||||
</div>
|
||||
</MainPanelLayout>
|
||||
);
|
||||
@@ -215,7 +262,7 @@ export default function AppsView() {
|
||||
<div className="bg-background-primary px-8 pb-8 pt-16">
|
||||
<div className="flex flex-col page-transition">
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<h1 className="text-4xl font-light">Apps</h1>
|
||||
<h1 className="text-4xl font-light">{intl.formatMessage(i18n.title)}</h1>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -223,13 +270,12 @@ export default function AppsView() {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
Import App
|
||||
{intl.formatMessage(i18n.importApp)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-text-secondary mb-2">
|
||||
Applications from your MCP servers and Apps build by goose itself. You can ask it to
|
||||
create new apps through the chat interface and they will appear here.
|
||||
{intl.formatMessage(i18n.description)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,16 +284,14 @@ export default function AppsView() {
|
||||
<div className="flex-1 overflow-y-auto px-8 pb-8">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<p className="text-text-secondary">Loading apps...</p>
|
||||
<p className="text-text-secondary">{intl.formatMessage(i18n.loading)}</p>
|
||||
</div>
|
||||
) : apps.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-medium mb-2">No apps available</h3>
|
||||
<h3 className="text-lg font-medium mb-2">{intl.formatMessage(i18n.noAppsTitle)}</h3>
|
||||
<p className="text-sm text-text-secondary">
|
||||
Open a chat and ask goose for the app you want to have. It can build one for you
|
||||
and that will appear here. Or if somebody shared an app, you can import it using
|
||||
the button above.
|
||||
{intl.formatMessage(i18n.noAppsDescription)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -269,7 +313,7 @@ export default function AppsView() {
|
||||
)}
|
||||
{app.mcpServers && app.mcpServers.length > 0 && (
|
||||
<span className="inline-block px-2 py-1 text-xs bg-background-secondary text-text-secondary rounded">
|
||||
{isCustomApp ? 'Custom app' : app.mcpServers.join(', ')}
|
||||
{isCustomApp ? intl.formatMessage(i18n.customApp) : app.mcpServers.join(', ')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -281,7 +325,7 @@ export default function AppsView() {
|
||||
className="flex items-center gap-2 flex-1"
|
||||
>
|
||||
<Play className="h-4 w-4" />
|
||||
Launch
|
||||
{intl.formatMessage(i18n.launch)}
|
||||
</Button>
|
||||
{isCustomApp && (
|
||||
<Button
|
||||
|
||||
@@ -4,8 +4,25 @@ import McpAppRenderer from '../McpApps/McpAppRenderer';
|
||||
import { startAgent, resumeAgent, listApps, stopAgent } from '../../api';
|
||||
import { formatAppName } from '../../utils/conversionUtils';
|
||||
import { errorMessage } from '../../utils/conversionUtils';
|
||||
import { defineMessages, useIntl } from '../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
failedToLoad: {
|
||||
id: 'standaloneAppView.failedToLoad',
|
||||
defaultMessage: 'Failed to Load App',
|
||||
},
|
||||
initializing: {
|
||||
id: 'standaloneAppView.initializing',
|
||||
defaultMessage: 'Initializing app...',
|
||||
},
|
||||
missingParams: {
|
||||
id: 'standaloneAppView.missingParams',
|
||||
defaultMessage: 'Missing required parameters',
|
||||
},
|
||||
});
|
||||
|
||||
export default function StandaloneAppView() {
|
||||
const intl = useIntl();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||
const [cachedHtml, setCachedHtml] = useState<string | null>(null);
|
||||
@@ -25,7 +42,7 @@ export default function StandaloneAppView() {
|
||||
resourceUri === 'undefined' ||
|
||||
extensionName === 'undefined'
|
||||
) {
|
||||
setError('Missing required parameters');
|
||||
setError(intl.formatMessage(i18n.missingParams));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -50,7 +67,7 @@ export default function StandaloneAppView() {
|
||||
}
|
||||
|
||||
loadCachedHtml();
|
||||
}, [resourceUri, extensionName]);
|
||||
}, [resourceUri, extensionName, intl]);
|
||||
|
||||
useEffect(() => {
|
||||
async function initSession() {
|
||||
@@ -122,7 +139,7 @@ export default function StandaloneAppView() {
|
||||
padding: '24px',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ color: 'var(--text-error, #ef4444)' }}>Failed to Load App</h2>
|
||||
<h2 style={{ color: 'var(--text-error, #ef4444)' }}>{intl.formatMessage(i18n.failedToLoad)}</h2>
|
||||
<p style={{ color: 'var(--color-text-secondary, #6b7280)' }}>{error}</p>
|
||||
</div>
|
||||
);
|
||||
@@ -139,7 +156,7 @@ export default function StandaloneAppView() {
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<p style={{ color: 'var(--color-text-secondary, #6b7280)' }}>Initializing app...</p>
|
||||
<p style={{ color: 'var(--color-text-secondary, #6b7280)' }}>{intl.formatMessage(i18n.initializing)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user