diff --git a/src/lib/providers.ts b/src/lib/providers.ts index e39c29e..2f21451 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -1,3 +1,5 @@ +import type { LanguageCode } from '../types/runtime'; + export const PROVIDER_TYPES = [ 'anthropic', 'openai', @@ -57,6 +59,8 @@ export interface ProviderTypeInfo { name: string; icon: string; placeholder: string; + placeholderZh?: string; + placeholderJa?: string; model?: string; requiresApiKey: boolean; defaultBaseUrl?: string; @@ -167,12 +171,14 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'minimax-portal', name: 'MiniMax (Global)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultModelId: 'MiniMax-M2.7', showModelId: true, showModelIdInDevModeOnly: true, modelIdPlaceholder: 'MiniMax-M2.7', apiKeyUrl: 'https://platform.minimax.io' }, { id: 'modelstudio', name: 'Model Studio', icon: '☁️', placeholder: 'sk-...', model: 'Qwen', requiresApiKey: true, defaultBaseUrl: 'https://coding.dashscope.aliyuncs.com/v1', showBaseUrl: true, defaultModelId: 'qwen3.5-plus', showModelId: true, showModelIdInDevModeOnly: true, modelIdPlaceholder: 'qwen3.5-plus', apiKeyUrl: 'https://bailian.console.aliyun.com/', hidden: true }, { id: 'ark', name: 'ByteDance Ark', icon: 'A', placeholder: 'your-ark-api-key', model: 'Doubao', requiresApiKey: true, defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'ep-20260228000000-xxxxx', docsUrl: 'https://www.volcengine.com/', codePlanPresetBaseUrl: 'https://ark.cn-beijing.volces.com/api/coding/v3', codePlanPresetModelId: 'ark-code-latest', codePlanDocsUrl: 'https://www.volcengine.com/docs/82379/1928261?lang=zh' }, - { id: 'ollama', name: 'Ollama', icon: '🦙', placeholder: 'Not required', requiresApiKey: false, defaultBaseUrl: 'http://localhost:11434/v1', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'qwen3:latest' }, + { id: 'ollama', name: 'Ollama', icon: '🦙', placeholder: 'Not required', placeholderZh: '无需填写', placeholderJa: '不要', requiresApiKey: false, defaultBaseUrl: 'http://localhost:11434/v1', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'qwen3:latest' }, { id: 'custom', name: 'Custom', icon: '⚙️', placeholder: 'API key...', + placeholderZh: 'API Key...', + placeholderJa: 'API キー...', requiresApiKey: true, showBaseUrl: true, showModelId: true, @@ -198,7 +204,7 @@ export function getProviderTypeInfo(type: ProviderType): ProviderTypeInfo | unde export function getProviderDocsUrl( provider: Pick | undefined, - language: string, + language: string | LanguageCode, ): string | undefined { if (!provider?.docsUrl) { return undefined; @@ -209,6 +215,22 @@ export function getProviderDocsUrl( return provider.docsUrl; } +export function getProviderPlaceholder( + provider: Pick | undefined, + language: string | LanguageCode, +): string | undefined { + if (!provider?.placeholder) { + return undefined; + } + if (language.startsWith('zh') && provider.placeholderZh) { + return provider.placeholderZh; + } + if (language.startsWith('ja') && provider.placeholderJa) { + return provider.placeholderJa; + } + return provider.placeholder; +} + export function shouldShowProviderModelId( provider: Pick | undefined, devModeUnlocked: boolean, diff --git a/src/pages/Agents/index.tsx b/src/pages/Agents/index.tsx index 63c13e9..ad89cb5 100644 --- a/src/pages/Agents/index.tsx +++ b/src/pages/Agents/index.tsx @@ -289,12 +289,12 @@ export default function AgentsPage() {

{pageCopy.title}

-

+

{pageCopy.subtitle}

diff --git a/src/pages/Models/components/DialogSurface.tsx b/src/pages/Models/components/DialogSurface.tsx index a47be36..8fd7fa9 100644 --- a/src/pages/Models/components/DialogSurface.tsx +++ b/src/pages/Models/components/DialogSurface.tsx @@ -4,6 +4,7 @@ type DialogSurfaceProps = { open: boolean; title: string; subtitle?: string; + closeLabel?: string; widthClassName?: string; onClose: () => void; children: ReactNode; @@ -13,6 +14,7 @@ export default function DialogSurface({ open, title, subtitle, + closeLabel = 'Close dialog', widthClassName = 'max-w-[560px]', onClose, children, @@ -50,7 +52,7 @@ export default function DialogSurface({ type="button" className="mt-0.5 rounded-full p-1 text-[#99A0AE] transition-colors hover:text-[#171717] dark:hover:text-[#f3f4f6]" onClick={onClose} - aria-label="Close dialog" + aria-label={closeLabel} > diff --git a/src/pages/Models/components/ModelsSection.tsx b/src/pages/Models/components/ModelsSection.tsx index 9779948..ee9c063 100644 --- a/src/pages/Models/components/ModelsSection.tsx +++ b/src/pages/Models/components/ModelsSection.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react'; import { modelsStore, useModelsStore } from '../../../stores'; +import { useModelsCopy } from '../copy'; const CHIP_CLASS_NAME = [ 'rounded-full border px-2.5 py-1 text-[11px] leading-none', @@ -8,6 +9,7 @@ const CHIP_CLASS_NAME = [ export default function ModelsSection() { const modelsState = useModelsStore(); + const t = useModelsCopy(); useEffect(() => { void modelsStore.init(); @@ -18,10 +20,10 @@ export default function ModelsSection() {

- Models Snapshot + {t('models.snapshot.title')}

- 当前本地 `/api/models` 快照与 `mainSessionKey` 映射。 + {t('models.snapshot.subtitle')}

@@ -56,18 +58,18 @@ export default function ModelsSection() {
{model.isDefault ? ( - 默认 + {t('models.snapshot.defaultBadge')} ) : null}
- Provider: {model.providerAccountId || '--'} - Model: {model.modelDisplay || '--'} + {t('models.snapshot.provider')}: {model.providerAccountId || '--'} + {t('models.snapshot.model')}: {model.modelDisplay || '--'}
-
mainSessionKey
+
{t('models.snapshot.mainSessionKey')}
{model.mainSessionKey}
@@ -75,7 +77,7 @@ export default function ModelsSection() { {!modelsState.loading && modelsState.models.length === 0 ? (
- 当前还没有可用模型。先在下方配置 provider 账号后,这里会自动生成可路由的 model snapshot。 + {t('models.snapshot.empty')}
) : null} diff --git a/src/pages/Models/components/ProviderEditorDialog.tsx b/src/pages/Models/components/ProviderEditorDialog.tsx index cd582e5..b37c7a8 100644 --- a/src/pages/Models/components/ProviderEditorDialog.tsx +++ b/src/pages/Models/components/ProviderEditorDialog.tsx @@ -1,4 +1,7 @@ import { useEffect, useState } from 'react'; +import { getProviderDocsUrl, getProviderPlaceholder } from '../../../lib/providers'; +import { useLocale } from '../../../i18n'; +import { useModelsCopy } from '../copy'; import DialogSurface from './DialogSurface'; import type { ProviderEditorValues, ProviderListItem } from './provider-types'; @@ -27,6 +30,8 @@ export default function ProviderEditorDialog({ onSave, onSwitchProvider, }: ProviderEditorDialogProps) { + const locale = useLocale(); + const t = useModelsCopy(); const [label, setLabel] = useState(''); const [apiKey, setApiKey] = useState(''); const [baseUrl, setBaseUrl] = useState(''); @@ -47,19 +52,22 @@ export default function ProviderEditorDialog({ const isNew = Boolean(item.isNew); const showBaseUrl = Boolean(provider?.showBaseUrl); const showModel = Boolean(provider?.showModelId); + const docsUrl = getProviderDocsUrl(provider, locale); + const apiKeyPlaceholder = getProviderPlaceholder(provider, locale) || t('models.providers.editor.apiKeyPlaceholder'); return (
- {provider?.icon || 'AI'} + {provider?.icon || t('models.common.ai')}
@@ -68,11 +76,11 @@ export default function ProviderEditorDialog({
- {provider?.docsUrl ? ( - - View docs + {docsUrl ? ( + + {t('models.providers.editor.viewDocs')} ) : null}
@@ -88,41 +96,41 @@ export default function ProviderEditorDialog({
{showBaseUrl ? ( @@ -131,12 +139,12 @@ export default function ProviderEditorDialog({ {showModel ? ( @@ -150,7 +158,7 @@ export default function ProviderEditorDialog({ onClick={() => onSave({ label, apiKey, baseUrl, model })} disabled={saving} > - {saving ? 'Saving...' : isNew ? 'Add Provider' : 'Save'} + {saving ? t('models.providers.editor.saving') : isNew ? t('models.providers.editor.add') : t('models.providers.editor.save')}
diff --git a/src/pages/Models/components/ProviderPickerDialog.tsx b/src/pages/Models/components/ProviderPickerDialog.tsx index 2ee450b..27b2f2f 100644 --- a/src/pages/Models/components/ProviderPickerDialog.tsx +++ b/src/pages/Models/components/ProviderPickerDialog.tsx @@ -1,4 +1,5 @@ import type { ProviderTypeInfo } from '../../../lib/providers'; +import { useModelsCopy } from '../copy'; import DialogSurface from './DialogSurface'; type ProviderPickerDialogProps = { @@ -14,12 +15,15 @@ export default function ProviderPickerDialog({ onClose, onSelect, }: ProviderPickerDialogProps) { + const t = useModelsCopy(); + return (
@@ -31,7 +35,7 @@ export default function ProviderPickerDialog({ onClick={() => onSelect(provider)} >
- {provider.icon || 'AI'} + {provider.icon || t('models.common.ai')}
{provider.name} diff --git a/src/pages/Models/components/ProvidersSection.tsx b/src/pages/Models/components/ProvidersSection.tsx index a598277..8c7efa8 100644 --- a/src/pages/Models/components/ProvidersSection.tsx +++ b/src/pages/Models/components/ProvidersSection.tsx @@ -12,6 +12,7 @@ import { isRuntimeChangedGatewayEvent, runtimeEventHasTopic } from '../../../lib import ProviderEditorDialog from './ProviderEditorDialog'; import ProviderPickerDialog from './ProviderPickerDialog'; import type { DisplayVendor, ProviderEditorValues, ProviderListItem } from './provider-types'; +import { useModelsCopy } from '../copy'; type NoticeState = { tone: 'success' | 'error'; @@ -111,6 +112,7 @@ function buildProviderAccountId(providerId: ProviderTypeInfo['id']): string { } export default function ProvidersSection() { + const t = useModelsCopy(); const [loading, setLoading] = useState(true); const [items, setItems] = useState([]); const [vendors, setVendors] = useState(VISIBLE_PROVIDERS); @@ -153,7 +155,7 @@ export default function ProvidersSection() { } catch (error) { setNotice({ tone: 'error', - message: formatErrorMessage(error, 'Failed to load provider settings.'), + message: formatErrorMessage(error, t('models.providers.notices.loadError')), }); } finally { if (showLoading) { @@ -182,17 +184,17 @@ export default function ProvidersSection() { }); await loadProviders(false); setDefaultAccountId(accountId); - setNotice({ tone: 'success', message: 'Default provider set successfully.' }); + setNotice({ tone: 'success', message: t('models.providers.notices.defaultSuccess') }); } catch (error) { setNotice({ tone: 'error', - message: formatErrorMessage(error, 'Failed to set the default provider.'), + message: formatErrorMessage(error, t('models.providers.notices.defaultError')), }); } } async function handleDelete(accountId: string): Promise { - const confirmed = window.confirm('Are you sure you want to delete this provider?'); + const confirmed = window.confirm(t('models.providers.confirmDelete')); if (!confirmed) return; try { @@ -200,11 +202,11 @@ export default function ProvidersSection() { method: 'DELETE', }); await loadProviders(false); - setNotice({ tone: 'success', message: 'Provider deleted successfully.' }); + setNotice({ tone: 'success', message: t('models.providers.notices.deleteSuccess') }); } catch (error) { setNotice({ tone: 'error', - message: formatErrorMessage(error, 'Failed to delete this provider.'), + message: formatErrorMessage(error, t('models.providers.notices.deleteError')), }); } } @@ -277,7 +279,7 @@ export default function ProvidersSection() { }); } - setNotice({ tone: 'success', message: 'Provider saved successfully.' }); + setNotice({ tone: 'success', message: t('models.providers.notices.saveSuccess') }); } else { await hostApiFetch<{ success: boolean; error?: string }>( `/api/provider-accounts/${encodeURIComponent(editorItem.account.id)}`, @@ -295,13 +297,13 @@ export default function ProvidersSection() { }, ); - setNotice({ tone: 'success', message: 'Provider saved successfully.' }); + setNotice({ tone: 'success', message: t('models.providers.notices.saveSuccess') }); } await loadProviders(false); setEditorItem(null); } catch (error) { - setEditorError(formatErrorMessage(error, 'Failed to save this provider.')); + setEditorError(formatErrorMessage(error, t('models.providers.notices.saveError'))); } finally { setSaving(false); } @@ -311,9 +313,9 @@ export default function ProvidersSection() {
-

AI Providers

+

{t('models.providers.title')}

- Manage your AI models and API keys. + {t('models.providers.subtitle')}

@@ -325,7 +327,7 @@ export default function ProvidersSection() { setPickerOpen(true); }} > - Add Provider + {t('models.providers.add')}
@@ -345,7 +347,7 @@ export default function ProvidersSection() { {loading ? (
- Loading provider settings... + {t('models.providers.loading')}
) : null} @@ -363,7 +365,7 @@ export default function ProvidersSection() {
- {item.vendor?.icon || 'AI'} + {item.vendor?.icon || t('models.common.ai')}
@@ -373,7 +375,7 @@ export default function ProvidersSection() { {isDefault ? ( - Default + {t('models.providers.defaultBadge')} ) : null}
@@ -393,7 +395,7 @@ export default function ProvidersSection() { configured ? 'text-green-600 dark:text-green-400' : 'text-orange-500 dark:text-orange-300', ].join(' ')} > - {configured ? 'Configured' : 'Not Configured'} + {configured ? t('models.providers.configured') : t('models.providers.notConfigured')}
@@ -408,12 +410,12 @@ export default function ProvidersSection() { void handleSetDefault(item.account.id); }} > - Set Default + {t('models.providers.setDefault')} ) : null}
@@ -433,7 +435,7 @@ export default function ProvidersSection() { {items.length === 0 ? (
- No providers configured yet. Click "Add Provider" to start. + {t('models.providers.empty')}
) : null}
diff --git a/src/pages/Models/components/RequestContentDialog.tsx b/src/pages/Models/components/RequestContentDialog.tsx index 06a0062..3db4551 100644 --- a/src/pages/Models/components/RequestContentDialog.tsx +++ b/src/pages/Models/components/RequestContentDialog.tsx @@ -1,3 +1,5 @@ +import { useLocale } from '../../../i18n'; +import { useModelsCopy } from '../copy'; import DialogSurface from './DialogSurface'; import type { UsageHistoryEntry } from '../usage-history'; @@ -7,13 +9,13 @@ type RequestContentDialogProps = { onClose: () => void; }; -function formatUsageTimestamp(timestamp?: string): string { +function formatUsageTimestamp(timestamp: string | undefined, locale: string): string { if (!timestamp) return ''; const date = new Date(timestamp); if (Number.isNaN(date.getTime())) return timestamp; - return new Intl.DateTimeFormat(undefined, { + return new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric', hour: '2-digit', @@ -26,12 +28,16 @@ export default function RequestContentDialog({ entry, onClose, }: RequestContentDialogProps) { + const locale = useLocale(); + const t = useModelsCopy(); + return (
diff --git a/src/pages/Models/components/UsageBarChart.tsx b/src/pages/Models/components/UsageBarChart.tsx index 4ee835c..f004caf 100644 --- a/src/pages/Models/components/UsageBarChart.tsx +++ b/src/pages/Models/components/UsageBarChart.tsx @@ -1,3 +1,4 @@ +import { useLocale } from '../../../i18n'; import type { UsageGroup } from '../usage-history'; type UsageBarChartProps = { @@ -9,8 +10,8 @@ type UsageBarChartProps = { cacheLabel: string; }; -function formatTokenCount(value: number): string { - return new Intl.NumberFormat().format(value); +function formatTokenCount(value: number, locale: string): string { + return new Intl.NumberFormat(locale).format(value); } export default function UsageBarChart({ @@ -21,6 +22,8 @@ export default function UsageBarChart({ outputLabel, cacheLabel, }: UsageBarChartProps) { + const locale = useLocale(); + if (groups.length === 0) { return (
@@ -53,7 +56,7 @@ export default function UsageBarChart({
{group.label} - {totalLabel}: {formatTokenCount(group.totalTokens)} + {totalLabel}: {formatTokenCount(group.totalTokens, locale)}
diff --git a/src/pages/Models/components/UsageHistorySection.tsx b/src/pages/Models/components/UsageHistorySection.tsx index e804ddf..7230b25 100644 --- a/src/pages/Models/components/UsageHistorySection.tsx +++ b/src/pages/Models/components/UsageHistorySection.tsx @@ -1,5 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { hostApiFetch } from '../../../lib/host-api'; +import { useLocale } from '../../../i18n'; +import { useModelsCopy } from '../copy'; import RequestContentDialog from './RequestContentDialog'; import UsageBarChart from './UsageBarChart'; import { @@ -32,15 +34,15 @@ function formatErrorMessage(error: unknown, fallback: string): string { return fallback; } -function formatTokenCount(value: number): string { - return new Intl.NumberFormat().format(value); +function formatTokenCount(value: number, locale: string): string { + return new Intl.NumberFormat(locale).format(value); } -function formatUsageTimestamp(timestamp: string): string { +function formatUsageTimestamp(timestamp: string, locale: string): string { const date = new Date(timestamp); if (Number.isNaN(date.getTime())) return timestamp; - return new Intl.DateTimeFormat(undefined, { + return new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric', hour: '2-digit', @@ -71,10 +73,10 @@ function getUsageTotalClass(entry: UsageHistoryEntry): string { return 'font-bold text-[15px] text-[#171717] dark:text-[#f3f4f6]'; } -function getUsageTotalText(entry: UsageHistoryEntry): string { - if (entry.usageStatus === 'error') return 'Error'; +function getUsageTotalText(entry: UsageHistoryEntry, locale: string, errorLabel: string): string { + if (entry.usageStatus === 'error') return errorLabel; if (entry.usageStatus === 'missing') return '--'; - return formatTokenCount(entry.totalTokens); + return formatTokenCount(entry.totalTokens, locale); } function normalizeUsageEntry(entry: Partial): UsageHistoryEntry { @@ -137,6 +139,8 @@ function ToggleGroup({ } export default function UsageHistorySection() { + const locale = useLocale(); + const t = useModelsCopy(); const [groupBy, setGroupBy] = useState('model'); const [windowValue, setWindowValue] = useState('7d'); const [page, setPage] = useState(1); @@ -175,7 +179,7 @@ export default function UsageHistorySection() { setFetchState((current) => ({ ...current, status: 'done', - error: formatErrorMessage(error, 'Failed to load usage history.'), + error: formatErrorMessage(error, t('models.usage.loadError')), })); } finally { loadingRef.current = false; @@ -213,7 +217,10 @@ export default function UsageHistorySection() { preferStableOnEmpty: fetchState.status === 'loading', }); const filteredUsageHistory = filterUsageHistoryByWindow(visibleUsageHistory, windowValue); - const usageGroups = groupUsageHistory(filteredUsageHistory, groupBy); + const usageGroups = groupUsageHistory(filteredUsageHistory, groupBy, { + locale, + unknownLabel: t('models.common.unknown'), + }); const usagePageSize = 5; const usageTotalPages = Math.max(1, Math.ceil(filteredUsageHistory.length / usagePageSize)); const safeUsagePage = Math.min(page, usageTotalPages); @@ -228,7 +235,7 @@ export default function UsageHistorySection() { className="mb-6 text-2xl font-normal tracking-tight text-[#171717] dark:text-[#f3f4f6]" style={{ fontFamily: "Georgia, Cambria, 'Times New Roman', Times, serif" }} > - Token Usage History + {t('models.usage.title')} {fetchState.error ? ( @@ -240,19 +247,19 @@ export default function UsageHistorySection() { {usageLoading ? (
- Loading usage history... + {t('models.usage.loading')}
) : null} {!usageLoading && visibleUsageHistory.length === 0 ? (
- No usage history available. + {t('models.usage.empty')}
) : null} {!usageLoading && visibleUsageHistory.length > 0 && filteredUsageHistory.length === 0 ? (
- No usage history in the selected time window. + {t('models.usage.emptyWindow')}
) : null} @@ -264,8 +271,8 @@ export default function UsageHistorySection() { value={groupBy} onChange={setGroupBy} options={[ - { value: 'model', label: 'By Model' }, - { value: 'day', label: 'By Time' }, + { value: 'model', label: t('models.usage.groupByModel') }, + { value: 'day', label: t('models.usage.groupByTime') }, ]} /> @@ -273,25 +280,25 @@ export default function UsageHistorySection() { value={windowValue} onChange={setWindowValue} options={[ - { value: '7d', label: 'Last 7 Days' }, - { value: '30d', label: 'Last 30 Days' }, - { value: 'all', label: 'All Time' }, + { value: '7d', label: t('models.usage.window7d') }, + { value: '30d', label: t('models.usage.window30d') }, + { value: 'all', label: t('models.usage.windowAll') }, ]} />

- {usageRefreshing ? 'Loading...' : `Showing ${filteredUsageHistory.length} records`} + {usageRefreshing ? t('models.common.loading') : t('models.usage.showingRecords', { count: filteredUsageHistory.length })}

@@ -303,7 +310,7 @@ export default function UsageHistorySection() {

- {entry.model || 'Unknown Model'} + {entry.model || t('models.common.unknownModel')}

{[formatUsageSource(entry.provider), formatUsageSource(entry.agentId), entry.sessionId] @@ -313,17 +320,17 @@ export default function UsageHistorySection() {

-

{getUsageTotalText(entry)}

+

{getUsageTotalText(entry, locale, t('models.usage.row.error'))}

{entry.usageStatus === 'missing' ? (

- No usage info + {t('models.usage.row.noUsageInfo')}

) : null} {entry.usageStatus === 'error' ? ( -

Error parsing usage

+

{t('models.usage.row.errorParsingUsage')}

) : null}

- {formatUsageTimestamp(entry.timestamp)} + {formatUsageTimestamp(entry.timestamp, locale)}

@@ -333,28 +340,28 @@ export default function UsageHistorySection() { <> - {`Input: ${formatTokenCount(entry.inputTokens)}`} + {t('models.usage.row.input', { count: formatTokenCount(entry.inputTokens, locale) })} - {`Output: ${formatTokenCount(entry.outputTokens)}`} + {t('models.usage.row.output', { count: formatTokenCount(entry.outputTokens, locale) })} {entry.cacheReadTokens > 0 ? ( - {`Cache Read: ${formatTokenCount(entry.cacheReadTokens)}`} + {t('models.usage.row.cacheRead', { count: formatTokenCount(entry.cacheReadTokens, locale) })} ) : null} {entry.cacheWriteTokens > 0 ? ( - {`Cache Write: ${formatTokenCount(entry.cacheWriteTokens)}`} + {t('models.usage.row.cacheWrite', { count: formatTokenCount(entry.cacheWriteTokens, locale) })} ) : null} ) : ( - {entry.usageStatus === 'missing' ? 'No usage reported' : 'Parse error'} + {entry.usageStatus === 'missing' ? t('models.usage.row.noUsageReported') : t('models.usage.row.parseError')} )} @@ -370,7 +377,7 @@ export default function UsageHistorySection() { className="ml-2 h-6 rounded-full border border-[#E5E8EE] px-2.5 text-[11.5px] text-[#525866] transition-colors hover:border-[#2B7FFF] hover:text-[#2B7FFF] dark:border-[#2a2a2d] dark:text-gray-300 dark:hover:border-[#3b82f6] dark:hover:text-white" onClick={() => setSelectedEntry(entry)} > - View Content + {t('models.usage.row.viewContent')} ) : null}
@@ -380,7 +387,7 @@ export default function UsageHistorySection() {

- {`Page ${safeUsagePage} of ${usageTotalPages}`} + {t('models.usage.pagination.pageOf', { page: safeUsagePage, total: usageTotalPages })}

@@ -393,7 +400,7 @@ export default function UsageHistorySection() { - Prev + {t('models.usage.pagination.prev')}