feat(desktop): add i18n infrastructure with react-intl (#8105)

This commit is contained in:
jh-block
2026-03-27 19:39:50 +01:00
committed by GitHub
parent d596113e72
commit a6765fbcd9
172 changed files with 13793 additions and 2054 deletions
+14 -1
View File
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { motion } from 'framer-motion';
import { Menu } from 'lucide-react';
import { defineMessages, useIntl } from '../../i18n';
import { Button } from '../ui/button';
import ChatSessionsContainer from '../ChatSessionsContainer';
import { useChatContext } from '../../contexts/ChatContext';
@@ -11,6 +12,17 @@ import { NAV_DIMENSIONS, Z_INDEX } from './constants';
import { cn } from '../../utils';
import { UserInput } from '../../types/message';
const i18n = defineMessages({
closeNavigation: {
id: 'appLayout.closeNavigation',
defaultMessage: 'Close navigation',
},
openNavigation: {
id: 'appLayout.openNavigation',
defaultMessage: 'Open navigation',
},
});
interface AppLayoutContentProps {
activeSessions: Array<{
sessionId: string;
@@ -19,6 +31,7 @@ interface AppLayoutContentProps {
}
const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) => {
const intl = useIntl();
const location = useLocation();
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
const chatContext = useChatContext();
@@ -199,7 +212,7 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
className="no-drag hover:!bg-background-tertiary"
variant="ghost"
size="xs"
title={isNavExpanded ? 'Close navigation' : 'Open navigation'}
title={isNavExpanded ? intl.formatMessage(i18n.closeNavigation) : intl.formatMessage(i18n.openNavigation)}
>
<Menu className="w-5 h-5" />
</Button>
@@ -1,11 +1,19 @@
import React, { useState } from 'react';
import { GripVertical, ChevronDown, ChevronRight, Plus } from 'lucide-react';
import { motion } from 'framer-motion';
import { defineMessages, useIntl } from '../../i18n';
import { cn } from '../../utils';
import { DropdownMenu, DropdownMenuTrigger } from '../ui/dropdown-menu';
import { ChatSessionsDropdown, SessionsList } from './navigation';
import type { NavigationRendererProps } from './navigation/types';
const i18n = defineMessages({
newChat: {
id: 'condensedRenderer.newChat',
defaultMessage: 'New Chat',
},
});
export const CondensedRenderer: React.FC<NavigationRendererProps> = ({
isOverlayMode,
navigationPosition,
@@ -26,6 +34,7 @@ export const CondensedRenderer: React.FC<NavigationRendererProps> = ({
drag,
navFocusRef,
}) => {
const intl = useIntl();
const [chatPopoverOpen, setChatPopoverOpen] = useState(false);
const isVertical = navigationPosition === 'left' || navigationPosition === 'right';
@@ -176,7 +185,7 @@ export const CondensedRenderer: React.FC<NavigationRendererProps> = ({
'bg-background-tertiary hover:bg-background-inverse hover:text-text-inverse',
'flex items-center justify-center'
)}
title="New Chat"
title={intl.formatMessage(i18n.newChat)}
>
<Plus className="w-4 h-4" />
</motion.button>
@@ -8,9 +8,21 @@ import {
import { SessionIndicators } from '../../SessionIndicators';
import { cn } from '../../../utils';
import { getSessionDisplayName, truncateMessage } from '../../../hooks/useNavigationSessions';
import { defineMessages, useIntl } from '../../../i18n';
import type { Session } from '../../../api';
import type { SessionStatus } from './types';
const i18n = defineMessages({
newChat: {
id: 'chatSessionsDropdown.newChat',
defaultMessage: 'New Chat',
},
showAll: {
id: 'chatSessionsDropdown.showAll',
defaultMessage: 'Show All',
},
});
interface ChatSessionsDropdownProps {
sessions: Session[];
activeSessionId?: string;
@@ -34,6 +46,7 @@ export const ChatSessionsDropdown: React.FC<ChatSessionsDropdownProps> = ({
onSessionClick,
onShowAll,
}) => {
const intl = useIntl();
return (
<DropdownMenuContent
className="w-64 p-1 bg-background-primary border-border-secondary rounded-lg shadow-lg"
@@ -47,7 +60,7 @@ export const ChatSessionsDropdown: React.FC<ChatSessionsDropdownProps> = ({
className="flex items-center gap-2 px-3 py-2 text-sm rounded-lg cursor-pointer"
>
<Plus className="w-4 h-4 flex-shrink-0" />
<span>New Chat</span>
<span>{intl.formatMessage(i18n.newChat)}</span>
</DropdownMenuItem>
{sessions.length > 0 && <DropdownMenuSeparator className="my-1" />}
@@ -96,7 +109,7 @@ export const ChatSessionsDropdown: React.FC<ChatSessionsDropdownProps> = ({
className="flex items-center gap-2 px-3 py-2 text-sm rounded-lg cursor-pointer text-text-secondary"
>
<History className="w-4 h-4 flex-shrink-0" />
<span>Show All</span>
<span>{intl.formatMessage(i18n.showAll)}</span>
</DropdownMenuItem>
</>
)}
@@ -8,6 +8,22 @@ import { getSessionDisplayName } from '../../../hooks/useNavigationSessions';
import { updateSessionName } from '../../../api';
import type { Session } from '../../../api';
import type { SessionStatus } from './types';
import { defineMessages, useIntl } from '../../../i18n';
const i18n = defineMessages({
startNewChat: {
id: 'sessionsList.startNewChat',
defaultMessage: 'Start New Chat',
},
untitledSession: {
id: 'sessionsList.untitledSession',
defaultMessage: 'Untitled session',
},
showAll: {
id: 'sessionsList.showAll',
defaultMessage: 'Show All',
},
});
interface SessionsListProps {
sessions: Session[];
@@ -32,6 +48,7 @@ export const SessionsList: React.FC<SessionsListProps> = ({
onNewChat,
onShowAll,
}) => {
const intl = useIntl();
const [editingSessionId, setEditingSessionId] = useState<string | null>(null);
const handleSaveSessionName = useCallback(
@@ -68,7 +85,7 @@ export const SessionsList: React.FC<SessionsListProps> = ({
>
<div className="w-4 flex-shrink-0" />
<Plus className="w-4 h-4 flex-shrink-0 text-text-secondary" />
<span className="text-text-primary">Start New Chat</span>
<span className="text-text-primary">{intl.formatMessage(i18n.startNewChat)}</span>
</div>
)}
@@ -105,7 +122,7 @@ export const SessionsList: React.FC<SessionsListProps> = ({
<InlineEditText
value={getSessionDisplayName(session)}
onSave={(newName) => handleSaveSessionName(session.id, newName)}
placeholder="Untitled session"
placeholder={intl.formatMessage(i18n.untitledSession)}
disabled={isStreaming}
singleClickEdit={false}
className="truncate text-text-primary flex-1 !px-0 !py-0 hover:bg-transparent"
@@ -134,7 +151,7 @@ export const SessionsList: React.FC<SessionsListProps> = ({
>
<div className="w-4 flex-shrink-0" />
<History className="w-4 h-4 flex-shrink-0" />
<span>Show All</span>
<span>{intl.formatMessage(i18n.showAll)}</span>
</div>
)}
</div>