mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-04-05 16:26:15 +02:00
feat(i18n): Integrate language switcher and translate home/settings pages
- Add useTranslation hook to SettingsLayout - Replace hardcoded menu items with translation keys - Add LanguageSwitcher component to settings header - Add i18n support to home page (Command Center) - Translate Maps, Easy Setup, Install Apps, Docs, Settings menu items - Update Alert components to use translation keys - Add new translation keys for menu items and descriptions Translation keys added: - home.easySetupDesc, home.installAppsDesc, home.docsDesc, home.settingsDesc - maps.viewOffline - settings.contentExplorer, settings.contentManager, settings.mapsManager - settings.checkUpdates, settings.supportProject, settings.serviceLogs Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a8f6fe353b
commit
56f49fe892
|
|
@ -39,7 +39,11 @@
|
||||||
"updateAvailable": "An update is available for Project N.O.M.A.D.!",
|
"updateAvailable": "An update is available for Project N.O.M.A.D.!",
|
||||||
"goToSettings": "Go to Settings",
|
"goToSettings": "Go to Settings",
|
||||||
"startHere": "Start here!",
|
"startHere": "Start here!",
|
||||||
"poweredBy": "Powered by"
|
"poweredBy": "Powered by",
|
||||||
|
"easySetupDesc": "Not sure where to start? Use the setup wizard to quickly configure your N.O.M.A.D.!",
|
||||||
|
"installAppsDesc": "Not seeing your favorite app? Install it here!",
|
||||||
|
"docsDesc": "Read Project N.O.M.A.D. manuals and guides",
|
||||||
|
"settingsDesc": "Configure your N.O.M.A.D. settings"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"maps": "Maps",
|
"maps": "Maps",
|
||||||
|
|
@ -50,6 +54,7 @@
|
||||||
},
|
},
|
||||||
"maps": {
|
"maps": {
|
||||||
"title": "Offline Maps",
|
"title": "Offline Maps",
|
||||||
|
"viewOffline": "View offline maps",
|
||||||
"search": "Search locations...",
|
"search": "Search locations...",
|
||||||
"noResults": "No results found",
|
"noResults": "No results found",
|
||||||
"loadingMap": "Loading map...",
|
"loadingMap": "Loading map...",
|
||||||
|
|
@ -69,10 +74,16 @@
|
||||||
"apps": "Apps",
|
"apps": "Apps",
|
||||||
"models": "Models",
|
"models": "Models",
|
||||||
"maps": "Maps",
|
"maps": "Maps",
|
||||||
|
"mapsManager": "Maps Manager",
|
||||||
"benchmark": "Benchmark",
|
"benchmark": "Benchmark",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
|
"checkUpdates": "Check for Updates",
|
||||||
"legal": "Legal",
|
"legal": "Legal",
|
||||||
"support": "Support"
|
"support": "Support",
|
||||||
|
"supportProject": "Support the Project",
|
||||||
|
"contentExplorer": "Content Explorer",
|
||||||
|
"contentManager": "Content Manager",
|
||||||
|
"serviceLogs": "Service Logs & Metrics"
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"title": "System Settings",
|
"title": "System Settings",
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,11 @@
|
||||||
"updateAvailable": "Project N.O.M.A.D. 有可用更新!",
|
"updateAvailable": "Project N.O.M.A.D. 有可用更新!",
|
||||||
"goToSettings": "前往设置",
|
"goToSettings": "前往设置",
|
||||||
"startHere": "从这里开始!",
|
"startHere": "从这里开始!",
|
||||||
"poweredBy": "技术支持"
|
"poweredBy": "技术支持",
|
||||||
|
"easySetupDesc": "不确定从哪里开始?使用设置向导快速配置您的 N.O.M.A.D.!",
|
||||||
|
"installAppsDesc": "没有看到您想要的应用?在这里安装!",
|
||||||
|
"docsDesc": "阅读 Project N.O.M.A.D. 手册和指南",
|
||||||
|
"settingsDesc": "配置您的 N.O.M.A.D. 设置"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"maps": "地图",
|
"maps": "地图",
|
||||||
|
|
@ -50,6 +54,7 @@
|
||||||
},
|
},
|
||||||
"maps": {
|
"maps": {
|
||||||
"title": "离线地图",
|
"title": "离线地图",
|
||||||
|
"viewOffline": "查看离线地图",
|
||||||
"search": "搜索位置...",
|
"search": "搜索位置...",
|
||||||
"noResults": "未找到结果",
|
"noResults": "未找到结果",
|
||||||
"loadingMap": "加载地图中...",
|
"loadingMap": "加载地图中...",
|
||||||
|
|
@ -69,10 +74,16 @@
|
||||||
"apps": "应用",
|
"apps": "应用",
|
||||||
"models": "模型",
|
"models": "模型",
|
||||||
"maps": "地图",
|
"maps": "地图",
|
||||||
|
"mapsManager": "地图管理器",
|
||||||
"benchmark": "基准测试",
|
"benchmark": "基准测试",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
|
"checkUpdates": "检查更新",
|
||||||
"legal": "法律",
|
"legal": "法律",
|
||||||
"support": "支持"
|
"support": "支持",
|
||||||
|
"supportProject": "支持项目",
|
||||||
|
"contentExplorer": "内容浏览器",
|
||||||
|
"contentManager": "内容管理器",
|
||||||
|
"serviceLogs": "服务日志和指标"
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"title": "系统设置",
|
"title": "系统设置",
|
||||||
|
|
|
||||||
|
|
@ -9,47 +9,67 @@ import {
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconTerminal2,
|
IconTerminal2,
|
||||||
IconWand,
|
IconWand,
|
||||||
IconZoom
|
IconZoom,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { usePage } from '@inertiajs/react'
|
import { usePage } from '@inertiajs/react'
|
||||||
import StyledSidebar from '~/components/StyledSidebar'
|
import StyledSidebar from '~/components/StyledSidebar'
|
||||||
import { getServiceLink } from '~/lib/navigation'
|
import { getServiceLink } from '~/lib/navigation'
|
||||||
import useServiceInstalledStatus from '~/hooks/useServiceInstalledStatus'
|
import useServiceInstalledStatus from '~/hooks/useServiceInstalledStatus'
|
||||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||||
|
import LanguageSwitcher from '~/components/LanguageSwitcher'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
||||||
const aiAssistantInstallStatus = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
const aiAssistantInstallStatus = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
...(aiAssistantInstallStatus.isInstalled ? [{ name: aiAssistantName, href: '/settings/models', icon: IconWand, current: false }] : []),
|
...(aiAssistantInstallStatus.isInstalled
|
||||||
{ name: 'Apps', href: '/settings/apps', icon: IconTerminal2, current: false },
|
? [{ name: aiAssistantName, href: '/settings/models', icon: IconWand, current: false }]
|
||||||
{ name: 'Benchmark', href: '/settings/benchmark', icon: IconChartBar, current: false },
|
: []),
|
||||||
{ name: 'Content Explorer', href: '/settings/zim/remote-explorer', icon: IconZoom, current: false },
|
{ name: t('settings.apps'), href: '/settings/apps', icon: IconTerminal2, current: false },
|
||||||
{ name: 'Content Manager', href: '/settings/zim', icon: IconFolder, current: false },
|
|
||||||
{ name: 'Maps Manager', href: '/settings/maps', icon: IconMapRoute, current: false },
|
|
||||||
{
|
{
|
||||||
name: 'Service Logs & Metrics',
|
name: t('settings.benchmark'),
|
||||||
|
href: '/settings/benchmark',
|
||||||
|
icon: IconChartBar,
|
||||||
|
current: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('settings.contentExplorer'),
|
||||||
|
href: '/settings/zim/remote-explorer',
|
||||||
|
icon: IconZoom,
|
||||||
|
current: false,
|
||||||
|
},
|
||||||
|
{ name: t('settings.contentManager'), href: '/settings/zim', icon: IconFolder, current: false },
|
||||||
|
{ name: t('settings.mapsManager'), href: '/settings/maps', icon: IconMapRoute, current: false },
|
||||||
|
{
|
||||||
|
name: t('settings.serviceLogs'),
|
||||||
href: getServiceLink('9999'),
|
href: getServiceLink('9999'),
|
||||||
icon: IconDashboard,
|
icon: IconDashboard,
|
||||||
current: false,
|
current: false,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Check for Updates',
|
name: t('settings.checkUpdates'),
|
||||||
href: '/settings/update',
|
href: '/settings/update',
|
||||||
icon: IconArrowBigUpLines,
|
icon: IconArrowBigUpLines,
|
||||||
current: false,
|
current: false,
|
||||||
},
|
},
|
||||||
{ name: 'System', href: '/settings/system', icon: IconSettings, current: false },
|
{ name: t('settings.system'), href: '/settings/system', icon: IconSettings, current: false },
|
||||||
{ name: 'Support the Project', href: '/settings/support', icon: IconHeart, current: false },
|
{ name: t('settings.support'), href: '/settings/support', icon: IconHeart, current: false },
|
||||||
{ name: 'Legal Notices', href: '/settings/legal', icon: IconGavel, current: false },
|
{ name: t('settings.legal'), href: '/settings/legal', icon: IconGavel, current: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-row bg-surface-secondary/90">
|
<div className="min-h-screen flex flex-row bg-surface-secondary/90">
|
||||||
<StyledSidebar title="Settings" items={navigation} />
|
<StyledSidebar title={t('settings.title')} items={navigation} />
|
||||||
{children}
|
<div className="flex-1 flex flex-col">
|
||||||
|
<div className="p-4 border-b border-desert-stone-light bg-desert-white">
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,57 +15,57 @@ import { useUpdateAvailable } from '~/hooks/useUpdateAvailable'
|
||||||
import { useSystemSetting } from '~/hooks/useSystemSetting'
|
import { useSystemSetting } from '~/hooks/useSystemSetting'
|
||||||
import Alert from '~/components/Alert'
|
import Alert from '~/components/Alert'
|
||||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
// Maps is a Core Capability (display_order: 4)
|
// Maps is a Core Capability (display_order: 4)
|
||||||
const MAPS_ITEM = {
|
const getMapsItem = (t: (key: string) => string) => ({
|
||||||
label: 'Maps',
|
label: t('menu.maps'),
|
||||||
to: '/maps',
|
to: '/maps',
|
||||||
target: '',
|
target: '',
|
||||||
description: 'View offline maps',
|
description: t('maps.viewOffline'),
|
||||||
icon: <IconMapRoute size={48} />,
|
icon: <IconMapRoute size={48} />,
|
||||||
installed: true,
|
installed: true,
|
||||||
displayOrder: 4,
|
displayOrder: 4,
|
||||||
poweredBy: null,
|
poweredBy: null,
|
||||||
}
|
})
|
||||||
|
|
||||||
// System items shown after all apps
|
// System items shown after all apps
|
||||||
const SYSTEM_ITEMS = [
|
const getSystemItems = (t: (key: string) => string) => [
|
||||||
{
|
{
|
||||||
label: 'Easy Setup',
|
label: t('menu.easySetup'),
|
||||||
to: '/easy-setup',
|
to: '/easy-setup',
|
||||||
target: '',
|
target: '',
|
||||||
description:
|
description: t('home.easySetupDesc'),
|
||||||
'Not sure where to start? Use the setup wizard to quickly configure your N.O.M.A.D.!',
|
|
||||||
icon: <IconBolt size={48} />,
|
icon: <IconBolt size={48} />,
|
||||||
installed: true,
|
installed: true,
|
||||||
displayOrder: 50,
|
displayOrder: 50,
|
||||||
poweredBy: null,
|
poweredBy: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Install Apps',
|
label: t('menu.installApps'),
|
||||||
to: '/settings/apps',
|
to: '/settings/apps',
|
||||||
target: '',
|
target: '',
|
||||||
description: 'Not seeing your favorite app? Install it here!',
|
description: t('home.installAppsDesc'),
|
||||||
icon: <IconPlus size={48} />,
|
icon: <IconPlus size={48} />,
|
||||||
installed: true,
|
installed: true,
|
||||||
displayOrder: 51,
|
displayOrder: 51,
|
||||||
poweredBy: null,
|
poweredBy: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Docs',
|
label: t('menu.docs'),
|
||||||
to: '/docs/home',
|
to: '/docs/home',
|
||||||
target: '',
|
target: '',
|
||||||
description: 'Read Project N.O.M.A.D. manuals and guides',
|
description: t('home.docsDesc'),
|
||||||
icon: <IconHelp size={48} />,
|
icon: <IconHelp size={48} />,
|
||||||
installed: true,
|
installed: true,
|
||||||
displayOrder: 52,
|
displayOrder: 52,
|
||||||
poweredBy: null,
|
poweredBy: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Settings',
|
label: t('menu.settings'),
|
||||||
to: '/settings/system',
|
to: '/settings/system',
|
||||||
target: '',
|
target: '',
|
||||||
description: 'Configure your N.O.M.A.D. settings',
|
description: t('home.settingsDesc'),
|
||||||
icon: <IconSettings size={48} />,
|
icon: <IconSettings size={48} />,
|
||||||
installed: true,
|
installed: true,
|
||||||
displayOrder: 53,
|
displayOrder: 53,
|
||||||
|
|
@ -89,15 +89,18 @@ export default function Home(props: {
|
||||||
services: ServiceSlim[]
|
services: ServiceSlim[]
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const items: DashboardItem[] = []
|
const items: DashboardItem[] = []
|
||||||
const updateInfo = useUpdateAvailable();
|
const updateInfo = useUpdateAvailable()
|
||||||
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
||||||
|
|
||||||
// Check if user has visited Easy Setup
|
// Check if user has visited Easy Setup
|
||||||
const { data: easySetupVisited } = useSystemSetting({
|
const { data: easySetupVisited } = useSystemSetting({
|
||||||
key: 'ui.hasVisitedEasySetup'
|
key: 'ui.hasVisitedEasySetup',
|
||||||
})
|
})
|
||||||
const shouldHighlightEasySetup = easySetupVisited?.value ? String(easySetupVisited.value) !== 'true' : false
|
const shouldHighlightEasySetup = easySetupVisited?.value
|
||||||
|
? String(easySetupVisited.value) !== 'true'
|
||||||
|
: false
|
||||||
|
|
||||||
// Add installed services (non-dependency services only)
|
// Add installed services (non-dependency services only)
|
||||||
props.system.services
|
props.system.services
|
||||||
|
|
@ -105,7 +108,10 @@ export default function Home(props: {
|
||||||
.forEach((service) => {
|
.forEach((service) => {
|
||||||
items.push({
|
items.push({
|
||||||
// Inject custom AI Assistant name if this is the chat service
|
// Inject custom AI Assistant name if this is the chat service
|
||||||
label: service.service_name === SERVICE_NAMES.OLLAMA && aiAssistantName ? aiAssistantName : (service.friendly_name || service.service_name),
|
label:
|
||||||
|
service.service_name === SERVICE_NAMES.OLLAMA && aiAssistantName
|
||||||
|
? aiAssistantName
|
||||||
|
: service.friendly_name || service.service_name,
|
||||||
to: service.ui_location ? getServiceLink(service.ui_location) : '#',
|
to: service.ui_location ? getServiceLink(service.ui_location) : '#',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
description:
|
description:
|
||||||
|
|
@ -123,38 +129,36 @@ export default function Home(props: {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add Maps as a Core Capability
|
// Add Maps as a Core Capability
|
||||||
items.push(MAPS_ITEM)
|
items.push(getMapsItem(t))
|
||||||
|
|
||||||
// Add system items
|
// Add system items
|
||||||
items.push(...SYSTEM_ITEMS)
|
items.push(...getSystemItems(t))
|
||||||
|
|
||||||
// Sort all items by display order
|
// Sort all items by display order
|
||||||
items.sort((a, b) => a.displayOrder - b.displayOrder)
|
items.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<Head title="Command Center" />
|
<Head title={t('home.title')} />
|
||||||
{
|
{updateInfo?.updateAvailable && (
|
||||||
updateInfo?.updateAvailable && (
|
<div className="flex justify-center items-center p-4 w-full">
|
||||||
<div className='flex justify-center items-center p-4 w-full'>
|
<Alert
|
||||||
<Alert
|
title={t('home.updateAvailable')}
|
||||||
title="An update is available for Project N.O.M.A.D.!"
|
type="info-inverted"
|
||||||
type="info-inverted"
|
variant="solid"
|
||||||
variant="solid"
|
className="w-full"
|
||||||
className="w-full"
|
buttonProps={{
|
||||||
buttonProps={{
|
variant: 'primary',
|
||||||
variant: 'primary',
|
children: t('home.goToSettings'),
|
||||||
children: 'Go to Settings',
|
icon: 'IconSettings',
|
||||||
icon: 'IconSettings',
|
onClick: () => router.visit('/settings/update'),
|
||||||
onClick: () => router.visit('/settings/update'),
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
const isEasySetup = item.label === 'Easy Setup'
|
const isEasySetup = item.label === t('menu.easySetup')
|
||||||
const shouldHighlight = isEasySetup && shouldHighlightEasySetup
|
const shouldHighlight = isEasySetup && shouldHighlightEasySetup
|
||||||
|
|
||||||
const tileContent = (
|
const tileContent = (
|
||||||
|
|
@ -166,13 +170,17 @@ export default function Home(props: {
|
||||||
style={{ animationDuration: '1.5s' }}
|
style={{ animationDuration: '1.5s' }}
|
||||||
></span>
|
></span>
|
||||||
<span className="relative inline-flex items-center rounded-full px-2.5 py-1 bg-desert-orange-light text-xs font-semibold text-white shadow-sm">
|
<span className="relative inline-flex items-center rounded-full px-2.5 py-1 bg-desert-orange-light text-xs font-semibold text-white shadow-sm">
|
||||||
Start here!
|
{t('home.startHere')}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center justify-center mb-2">{item.icon}</div>
|
<div className="flex items-center justify-center mb-2">{item.icon}</div>
|
||||||
<h3 className="font-bold text-2xl">{item.label}</h3>
|
<h3 className="font-bold text-2xl">{item.label}</h3>
|
||||||
{item.poweredBy && <p className="text-sm opacity-80">Powered by {item.poweredBy}</p>}
|
{item.poweredBy && (
|
||||||
|
<p className="text-sm opacity-80">
|
||||||
|
{t('home.poweredBy')} {item.poweredBy}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p className="xl:text-lg mt-2">{item.description}</p>
|
<p className="xl:text-lg mt-2">{item.description}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user