mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
feat(i18n): adicionar suporte a internacionalização com react-i18next
Implementa infraestrutura de i18n com suporte inicial a English e Português (Brasil). Seletor de idioma em Settings > System > Preferences. - Instala react-i18next e i18next - Cria arquivos de tradução (en, pt-BR) para common, home, settings, layout - Traduz layout (sidebar, header, footer), home e settings/system - Persiste idioma via localStorage + KVStore (ui.language) - DiskAlertBanner integrado com i18n Closes #486
This commit is contained in:
parent
67fc9290d8
commit
2d08533b7e
|
|
@ -1,6 +1,7 @@
|
|||
/// <reference path="../../adonisrc.ts" />
|
||||
/// <reference path="../../config/inertia.ts" />
|
||||
|
||||
import '~/lib/i18n'
|
||||
import '../css/app.css'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { createInertiaApp } from '@inertiajs/react'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePage } from '@inertiajs/react'
|
||||
import { UsePageProps } from '../../types/system'
|
||||
import ThemeToggle from '~/components/ThemeToggle'
|
||||
|
|
@ -6,6 +7,7 @@ import { IconBug } from '@tabler/icons-react'
|
|||
import DebugInfoModal from './DebugInfoModal'
|
||||
|
||||
export default function Footer() {
|
||||
const { t } = useTranslation('layout')
|
||||
const { appVersion } = usePage().props as unknown as UsePageProps
|
||||
const [debugModalOpen, setDebugModalOpen] = useState(false)
|
||||
|
||||
|
|
@ -13,7 +15,7 @@ export default function Footer() {
|
|||
<footer>
|
||||
<div className="flex items-center justify-center gap-3 border-t border-border-subtle py-4">
|
||||
<p className="text-sm/6 text-text-secondary">
|
||||
Project N.O.M.A.D. Command Center v{appVersion}
|
||||
{t('footer.version', { version: appVersion })}
|
||||
</p>
|
||||
<span className="text-gray-300">|</span>
|
||||
<button
|
||||
|
|
@ -21,7 +23,7 @@ export default function Footer() {
|
|||
className="text-sm/6 text-gray-500 hover:text-desert-green flex items-center gap-1 cursor-pointer"
|
||||
>
|
||||
<IconBug className="size-3.5" />
|
||||
Debug Info
|
||||
{t('footer.debugInfo')}
|
||||
</button>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,43 +12,45 @@ import {
|
|||
IconZoom
|
||||
} from '@tabler/icons-react'
|
||||
import { usePage } from '@inertiajs/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import StyledSidebar from '~/components/StyledSidebar'
|
||||
import { getServiceLink } from '~/lib/navigation'
|
||||
import useServiceInstalledStatus from '~/hooks/useServiceInstalledStatus'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||
|
||||
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
||||
const { t } = useTranslation('layout')
|
||||
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
||||
const aiAssistantInstallStatus = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
||||
|
||||
const navigation = [
|
||||
...(aiAssistantInstallStatus.isInstalled ? [{ name: aiAssistantName, href: '/settings/models', icon: IconWand, current: false }] : []),
|
||||
{ name: 'Apps', href: '/settings/apps', icon: IconTerminal2, current: false },
|
||||
{ name: 'Benchmark', href: '/settings/benchmark', icon: IconChartBar, current: false },
|
||||
{ name: 'Content Explorer', href: '/settings/zim/remote-explorer', icon: IconZoom, current: false },
|
||||
{ name: 'Content Manager', href: '/settings/zim', icon: IconFolder, current: false },
|
||||
{ name: 'Maps Manager', href: '/settings/maps', icon: IconMapRoute, current: false },
|
||||
{ name: t('sidebar.apps'), href: '/settings/apps', icon: IconTerminal2, current: false },
|
||||
{ name: t('sidebar.benchmark'), href: '/settings/benchmark', icon: IconChartBar, current: false },
|
||||
{ name: t('sidebar.contentExplorer'), href: '/settings/zim/remote-explorer', icon: IconZoom, current: false },
|
||||
{ name: t('sidebar.contentManager'), href: '/settings/zim', icon: IconFolder, current: false },
|
||||
{ name: t('sidebar.mapsManager'), href: '/settings/maps', icon: IconMapRoute, current: false },
|
||||
{
|
||||
name: 'Service Logs & Metrics',
|
||||
name: t('sidebar.serviceLogs'),
|
||||
href: getServiceLink('9999'),
|
||||
icon: IconDashboard,
|
||||
current: false,
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
name: 'Check for Updates',
|
||||
name: t('sidebar.checkUpdates'),
|
||||
href: '/settings/update',
|
||||
icon: IconArrowBigUpLines,
|
||||
current: false,
|
||||
},
|
||||
{ name: 'System', href: '/settings/system', icon: IconSettings, current: false },
|
||||
{ name: 'Support the Project', href: '/settings/support', icon: IconHeart, current: false },
|
||||
{ name: 'Legal Notices', href: '/settings/legal', icon: IconGavel, current: false },
|
||||
{ name: t('sidebar.system'), href: '/settings/system', icon: IconSettings, current: false },
|
||||
{ name: t('sidebar.support'), href: '/settings/support', icon: IconHeart, current: false },
|
||||
{ name: t('sidebar.legal'), href: '/settings/legal', icon: IconGavel, current: false },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-row bg-surface-secondary/90">
|
||||
<StyledSidebar title="Settings" items={navigation} />
|
||||
<StyledSidebar title={t('sidebar.settings')} items={navigation} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
46
admin/inertia/lib/i18n.ts
Normal file
46
admin/inertia/lib/i18n.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
// Import all translation files
|
||||
import enCommon from '~/locales/en/common.json'
|
||||
import enHome from '~/locales/en/home.json'
|
||||
import enSettings from '~/locales/en/settings.json'
|
||||
import enLayout from '~/locales/en/layout.json'
|
||||
|
||||
import ptBRCommon from '~/locales/pt-BR/common.json'
|
||||
import ptBRHome from '~/locales/pt-BR/home.json'
|
||||
import ptBRSettings from '~/locales/pt-BR/settings.json'
|
||||
import ptBRLayout from '~/locales/pt-BR/layout.json'
|
||||
|
||||
const savedLanguage = (() => {
|
||||
try {
|
||||
return localStorage.getItem('nomad:language') || 'en'
|
||||
} catch {
|
||||
return 'en'
|
||||
}
|
||||
})()
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
en: {
|
||||
common: enCommon,
|
||||
home: enHome,
|
||||
settings: enSettings,
|
||||
layout: enLayout,
|
||||
},
|
||||
'pt-BR': {
|
||||
common: ptBRCommon,
|
||||
home: ptBRHome,
|
||||
settings: ptBRSettings,
|
||||
layout: ptBRLayout,
|
||||
},
|
||||
},
|
||||
lng: savedLanguage,
|
||||
fallbackLng: 'en',
|
||||
defaultNS: 'common',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
})
|
||||
|
||||
export default i18n
|
||||
29
admin/inertia/locales/en/common.json
Normal file
29
admin/inertia/locales/en/common.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"buttons": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"goToSettings": "Go to Settings",
|
||||
"reinstall": "Reinstall"
|
||||
},
|
||||
"alerts": {
|
||||
"updateAvailable": "An update is available for Project N.O.M.A.D.!",
|
||||
"diskWarningTitle": "Disk Space Running Low",
|
||||
"diskCriticalTitle": "Disk Space Critically Low",
|
||||
"diskMessage": "Disk \"{{diskName}}\" is {{usage}}% full. Free up space to avoid issues with downloads and services.",
|
||||
"highMemoryTitle": "Very High Memory Usage Detected",
|
||||
"highMemoryMessage": "System memory usage exceeds 90%. Performance degradation may occur.",
|
||||
"gpuNotAccessibleTitle": "GPU Not Accessible to AI Assistant",
|
||||
"gpuNotAccessibleMessage": "Your system has an NVIDIA GPU, but the AI Assistant can't access it. AI is running on CPU only, which is significantly slower.",
|
||||
"fixReinstallAI": "Fix: Reinstall AI Assistant",
|
||||
"reinstallAIConfirmTitle": "Reinstall AI Assistant?",
|
||||
"reinstallAIConfirmMessage": "This will recreate the AI Assistant container with GPU support enabled. Your downloaded models will be preserved. The service will be briefly unavailable during reinstall.",
|
||||
"reinstallSuccess": "AI Assistant is being reinstalled with GPU support. This page will reload shortly.",
|
||||
"reinstallFailed": "Failed to reinstall: {{error}}"
|
||||
},
|
||||
"language": {
|
||||
"label": "Language",
|
||||
"en": "English",
|
||||
"pt-BR": "Português (Brasil)"
|
||||
}
|
||||
}
|
||||
24
admin/inertia/locales/en/home.json
Normal file
24
admin/inertia/locales/en/home.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"title": "Command Center",
|
||||
"easySetup": {
|
||||
"label": "Easy Setup",
|
||||
"description": "Not sure where to start? Use the setup wizard to quickly configure your N.O.M.A.D.!",
|
||||
"badge": "Start here!"
|
||||
},
|
||||
"installApps": {
|
||||
"label": "Install Apps",
|
||||
"description": "Not seeing your favorite app? Install it here!"
|
||||
},
|
||||
"docs": {
|
||||
"label": "Docs",
|
||||
"description": "Read Project N.O.M.A.D. manuals and guides"
|
||||
},
|
||||
"settingsCard": {
|
||||
"label": "Settings",
|
||||
"description": "Configure your N.O.M.A.D. settings"
|
||||
},
|
||||
"maps": {
|
||||
"label": "Maps",
|
||||
"description": "View offline maps"
|
||||
}
|
||||
}
|
||||
25
admin/inertia/locales/en/layout.json
Normal file
25
admin/inertia/locales/en/layout.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"header": {
|
||||
"commandCenter": "Command Center"
|
||||
},
|
||||
"nav": {
|
||||
"backToHome": "Back to Home"
|
||||
},
|
||||
"footer": {
|
||||
"version": "Project N.O.M.A.D. Command Center v{{version}}",
|
||||
"debugInfo": "Debug Info"
|
||||
},
|
||||
"sidebar": {
|
||||
"settings": "Settings",
|
||||
"apps": "Apps",
|
||||
"benchmark": "Benchmark",
|
||||
"contentExplorer": "Content Explorer",
|
||||
"contentManager": "Content Manager",
|
||||
"mapsManager": "Maps Manager",
|
||||
"serviceLogs": "Service Logs & Metrics",
|
||||
"checkUpdates": "Check for Updates",
|
||||
"system": "System",
|
||||
"support": "Support the Project",
|
||||
"legal": "Legal Notices"
|
||||
}
|
||||
}
|
||||
57
admin/inertia/locales/en/settings.json
Normal file
57
admin/inertia/locales/en/settings.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"system": {
|
||||
"title": "System Information",
|
||||
"subtitle": "Real-time monitoring and diagnostics",
|
||||
"lastUpdated": "Last updated: {{time}}",
|
||||
"refreshing": "Refreshing data every 30 seconds"
|
||||
},
|
||||
"sections": {
|
||||
"resourceUsage": "Resource Usage",
|
||||
"systemDetails": "System Details",
|
||||
"memoryAllocation": "Memory Allocation",
|
||||
"storageDevices": "Storage Devices",
|
||||
"systemStatus": "System Status",
|
||||
"preferences": "Preferences"
|
||||
},
|
||||
"labels": {
|
||||
"cpuUsage": "CPU Usage",
|
||||
"memoryUsage": "Memory Usage",
|
||||
"swapUsage": "Swap Usage",
|
||||
"cores": "{{count}} cores",
|
||||
"utilized": "{{percent}}% Utilized",
|
||||
"totalRam": "Total RAM",
|
||||
"usedRam": "Used RAM",
|
||||
"availableRam": "Available RAM",
|
||||
"systemUptime": "System Uptime",
|
||||
"cpuCores": "CPU Cores",
|
||||
"storageDevicesCount": "Storage Devices",
|
||||
"noStorageDetected": "No storage devices detected"
|
||||
},
|
||||
"os": {
|
||||
"distribution": "Distribution",
|
||||
"kernelVersion": "Kernel Version",
|
||||
"architecture": "Architecture",
|
||||
"hostname": "Hostname",
|
||||
"platform": "Platform"
|
||||
},
|
||||
"cpu": {
|
||||
"manufacturer": "Manufacturer",
|
||||
"brand": "Brand",
|
||||
"cores": "Cores",
|
||||
"physicalCores": "Physical Cores",
|
||||
"virtualization": "Virtualization",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"gpu": {
|
||||
"title": "Graphics",
|
||||
"model": "Model",
|
||||
"vendor": "Vendor",
|
||||
"vram": "VRAM"
|
||||
},
|
||||
"storage": {
|
||||
"normal": "Normal",
|
||||
"warningHigh": "Warning - Usage High",
|
||||
"criticalFull": "Critical - Disk Almost Full"
|
||||
}
|
||||
}
|
||||
29
admin/inertia/locales/pt-BR/common.json
Normal file
29
admin/inertia/locales/pt-BR/common.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"buttons": {
|
||||
"save": "Salvar",
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"goToSettings": "Ir para Configurações",
|
||||
"reinstall": "Reinstalar"
|
||||
},
|
||||
"alerts": {
|
||||
"updateAvailable": "Uma atualização está disponível para o Project N.O.M.A.D.!",
|
||||
"diskWarningTitle": "Espaço em disco ficando baixo",
|
||||
"diskCriticalTitle": "Espaço em disco criticamente baixo",
|
||||
"diskMessage": "O disco \"{{diskName}}\" está {{usage}}% cheio. Libere espaço para evitar problemas com downloads e serviços.",
|
||||
"highMemoryTitle": "Uso de memória muito alto detectado",
|
||||
"highMemoryMessage": "O uso de memória do sistema excede 90%. Pode haver degradação de desempenho.",
|
||||
"gpuNotAccessibleTitle": "GPU não acessível pelo Assistente de IA",
|
||||
"gpuNotAccessibleMessage": "Seu sistema possui uma GPU NVIDIA, mas o Assistente de IA não consegue acessá-la. A IA está rodando apenas na CPU, o que é significativamente mais lento.",
|
||||
"fixReinstallAI": "Corrigir: Reinstalar Assistente de IA",
|
||||
"reinstallAIConfirmTitle": "Reinstalar Assistente de IA?",
|
||||
"reinstallAIConfirmMessage": "Isso recriará o contêiner do Assistente de IA com suporte a GPU habilitado. Seus modelos baixados serão preservados. O serviço ficará brevemente indisponível durante a reinstalação.",
|
||||
"reinstallSuccess": "O Assistente de IA está sendo reinstalado com suporte a GPU. Esta página será recarregada em breve.",
|
||||
"reinstallFailed": "Falha ao reinstalar: {{error}}"
|
||||
},
|
||||
"language": {
|
||||
"label": "Idioma",
|
||||
"en": "English",
|
||||
"pt-BR": "Português (Brasil)"
|
||||
}
|
||||
}
|
||||
24
admin/inertia/locales/pt-BR/home.json
Normal file
24
admin/inertia/locales/pt-BR/home.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"title": "Central de Comando",
|
||||
"easySetup": {
|
||||
"label": "Configuração Rápida",
|
||||
"description": "Não sabe por onde começar? Use o assistente de configuração para configurar seu N.O.M.A.D. rapidamente!",
|
||||
"badge": "Comece aqui!"
|
||||
},
|
||||
"installApps": {
|
||||
"label": "Instalar Apps",
|
||||
"description": "Não encontra seu app favorito? Instale aqui!"
|
||||
},
|
||||
"docs": {
|
||||
"label": "Documentação",
|
||||
"description": "Leia os manuais e guias do Project N.O.M.A.D."
|
||||
},
|
||||
"settingsCard": {
|
||||
"label": "Configurações",
|
||||
"description": "Configure as opções do seu N.O.M.A.D."
|
||||
},
|
||||
"maps": {
|
||||
"label": "Mapas",
|
||||
"description": "Visualizar mapas offline"
|
||||
}
|
||||
}
|
||||
25
admin/inertia/locales/pt-BR/layout.json
Normal file
25
admin/inertia/locales/pt-BR/layout.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"header": {
|
||||
"commandCenter": "Central de Comando"
|
||||
},
|
||||
"nav": {
|
||||
"backToHome": "Voltar ao Início"
|
||||
},
|
||||
"footer": {
|
||||
"version": "Project N.O.M.A.D. Central de Comando v{{version}}",
|
||||
"debugInfo": "Informações de Depuração"
|
||||
},
|
||||
"sidebar": {
|
||||
"settings": "Configurações",
|
||||
"apps": "Aplicativos",
|
||||
"benchmark": "Benchmark",
|
||||
"contentExplorer": "Explorador de Conteúdo",
|
||||
"contentManager": "Gerenciador de Conteúdo",
|
||||
"mapsManager": "Gerenciador de Mapas",
|
||||
"serviceLogs": "Logs e Métricas de Serviços",
|
||||
"checkUpdates": "Verificar Atualizações",
|
||||
"system": "Sistema",
|
||||
"support": "Apoiar o Projeto",
|
||||
"legal": "Avisos Legais"
|
||||
}
|
||||
}
|
||||
57
admin/inertia/locales/pt-BR/settings.json
Normal file
57
admin/inertia/locales/pt-BR/settings.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"system": {
|
||||
"title": "Informações do Sistema",
|
||||
"subtitle": "Monitoramento e diagnósticos em tempo real",
|
||||
"lastUpdated": "Última atualização: {{time}}",
|
||||
"refreshing": "Atualizando dados a cada 30 segundos"
|
||||
},
|
||||
"sections": {
|
||||
"resourceUsage": "Uso de Recursos",
|
||||
"systemDetails": "Detalhes do Sistema",
|
||||
"memoryAllocation": "Alocação de Memória",
|
||||
"storageDevices": "Dispositivos de Armazenamento",
|
||||
"systemStatus": "Status do Sistema",
|
||||
"preferences": "Preferências"
|
||||
},
|
||||
"labels": {
|
||||
"cpuUsage": "Uso de CPU",
|
||||
"memoryUsage": "Uso de Memória",
|
||||
"swapUsage": "Uso de Swap",
|
||||
"cores": "{{count}} núcleos",
|
||||
"utilized": "{{percent}}% Utilizado",
|
||||
"totalRam": "RAM Total",
|
||||
"usedRam": "RAM Usada",
|
||||
"availableRam": "RAM Disponível",
|
||||
"systemUptime": "Tempo Ativo",
|
||||
"cpuCores": "Núcleos de CPU",
|
||||
"storageDevicesCount": "Dispositivos de Armazenamento",
|
||||
"noStorageDetected": "Nenhum dispositivo de armazenamento detectado"
|
||||
},
|
||||
"os": {
|
||||
"distribution": "Distribuição",
|
||||
"kernelVersion": "Versão do Kernel",
|
||||
"architecture": "Arquitetura",
|
||||
"hostname": "Nome do Host",
|
||||
"platform": "Plataforma"
|
||||
},
|
||||
"cpu": {
|
||||
"manufacturer": "Fabricante",
|
||||
"brand": "Modelo",
|
||||
"cores": "Núcleos",
|
||||
"physicalCores": "Núcleos Físicos",
|
||||
"virtualization": "Virtualização",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Desabilitado"
|
||||
},
|
||||
"gpu": {
|
||||
"title": "Placa de Vídeo",
|
||||
"model": "Modelo",
|
||||
"vendor": "Fabricante",
|
||||
"vram": "VRAM"
|
||||
},
|
||||
"storage": {
|
||||
"normal": "Normal",
|
||||
"warningHigh": "Alerta - Uso Elevado",
|
||||
"criticalFull": "Crítico - Disco Quase Cheio"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
IconWifiOff,
|
||||
} from '@tabler/icons-react'
|
||||
import { Head, usePage } from '@inertiajs/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppLayout from '~/layouts/AppLayout'
|
||||
import { getServiceLink } from '~/lib/navigation'
|
||||
import { ServiceSlim } from '../../types/services'
|
||||
|
|
@ -16,63 +17,6 @@ import { useSystemSetting } from '~/hooks/useSystemSetting'
|
|||
import Alert from '~/components/Alert'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||
|
||||
// Maps is a Core Capability (display_order: 4)
|
||||
const MAPS_ITEM = {
|
||||
label: 'Maps',
|
||||
to: '/maps',
|
||||
target: '',
|
||||
description: 'View offline maps',
|
||||
icon: <IconMapRoute size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 4,
|
||||
poweredBy: null,
|
||||
}
|
||||
|
||||
// System items shown after all apps
|
||||
const SYSTEM_ITEMS = [
|
||||
{
|
||||
label: 'Easy Setup',
|
||||
to: '/easy-setup',
|
||||
target: '',
|
||||
description:
|
||||
'Not sure where to start? Use the setup wizard to quickly configure your N.O.M.A.D.!',
|
||||
icon: <IconBolt size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 50,
|
||||
poweredBy: null,
|
||||
},
|
||||
{
|
||||
label: 'Install Apps',
|
||||
to: '/settings/apps',
|
||||
target: '',
|
||||
description: 'Not seeing your favorite app? Install it here!',
|
||||
icon: <IconPlus size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 51,
|
||||
poweredBy: null,
|
||||
},
|
||||
{
|
||||
label: 'Docs',
|
||||
to: '/docs/home',
|
||||
target: '',
|
||||
description: 'Read Project N.O.M.A.D. manuals and guides',
|
||||
icon: <IconHelp size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 52,
|
||||
poweredBy: null,
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
to: '/settings/system',
|
||||
target: '',
|
||||
description: 'Configure your N.O.M.A.D. settings',
|
||||
icon: <IconSettings size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 53,
|
||||
poweredBy: null,
|
||||
},
|
||||
]
|
||||
|
||||
interface DashboardItem {
|
||||
label: string
|
||||
to: string
|
||||
|
|
@ -89,6 +33,8 @@ export default function Home(props: {
|
|||
services: ServiceSlim[]
|
||||
}
|
||||
}) {
|
||||
const { t } = useTranslation('home')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const items: DashboardItem[] = []
|
||||
const updateInfo = useUpdateAvailable();
|
||||
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
||||
|
|
@ -99,6 +45,62 @@ export default function Home(props: {
|
|||
})
|
||||
const shouldHighlightEasySetup = easySetupVisited?.value ? String(easySetupVisited.value) !== 'true' : false
|
||||
|
||||
// Maps is a Core Capability (display_order: 4)
|
||||
const MAPS_ITEM: DashboardItem = {
|
||||
label: t('maps.label'),
|
||||
to: '/maps',
|
||||
target: '',
|
||||
description: t('maps.description'),
|
||||
icon: <IconMapRoute size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 4,
|
||||
poweredBy: null,
|
||||
}
|
||||
|
||||
// System items shown after all apps
|
||||
const SYSTEM_ITEMS: DashboardItem[] = [
|
||||
{
|
||||
label: t('easySetup.label'),
|
||||
to: '/easy-setup',
|
||||
target: '',
|
||||
description: t('easySetup.description'),
|
||||
icon: <IconBolt size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 50,
|
||||
poweredBy: null,
|
||||
},
|
||||
{
|
||||
label: t('installApps.label'),
|
||||
to: '/settings/apps',
|
||||
target: '',
|
||||
description: t('installApps.description'),
|
||||
icon: <IconPlus size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 51,
|
||||
poweredBy: null,
|
||||
},
|
||||
{
|
||||
label: t('docs.label'),
|
||||
to: '/docs/home',
|
||||
target: '',
|
||||
description: t('docs.description'),
|
||||
icon: <IconHelp size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 52,
|
||||
poweredBy: null,
|
||||
},
|
||||
{
|
||||
label: t('settingsCard.label'),
|
||||
to: '/settings/system',
|
||||
target: '',
|
||||
description: t('settingsCard.description'),
|
||||
icon: <IconSettings size={48} />,
|
||||
installed: true,
|
||||
displayOrder: 53,
|
||||
poweredBy: null,
|
||||
},
|
||||
]
|
||||
|
||||
// Add installed services (non-dependency services only)
|
||||
props.system.services
|
||||
.filter((service) => service.installed && service.ui_location)
|
||||
|
|
@ -133,18 +135,18 @@ export default function Home(props: {
|
|||
|
||||
return (
|
||||
<AppLayout>
|
||||
<Head title="Command Center" />
|
||||
<Head title={t('title')} />
|
||||
{
|
||||
updateInfo?.updateAvailable && (
|
||||
<div className='flex justify-center items-center p-4 w-full'>
|
||||
<Alert
|
||||
title="An update is available for Project N.O.M.A.D.!"
|
||||
title={tCommon('alerts.updateAvailable')}
|
||||
type="info-inverted"
|
||||
variant="solid"
|
||||
className="w-full"
|
||||
buttonProps={{
|
||||
variant: 'primary',
|
||||
children: 'Go to Settings',
|
||||
children: tCommon('buttons.goToSettings'),
|
||||
icon: 'IconSettings',
|
||||
onClick: () => {
|
||||
window.location.href = '/settings/update'
|
||||
|
|
@ -156,7 +158,7 @@ export default function Home(props: {
|
|||
}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
||||
{items.map((item) => {
|
||||
const isEasySetup = item.label === 'Easy Setup'
|
||||
const isEasySetup = item.to === '/easy-setup'
|
||||
const shouldHighlight = isEasySetup && shouldHighlightEasySetup
|
||||
|
||||
return (
|
||||
|
|
@ -169,7 +171,7 @@ export default function Home(props: {
|
|||
style={{ animationDuration: '1.5s' }}
|
||||
></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">
|
||||
Start here!
|
||||
{t('easySetup.badge')}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { useState } from 'react'
|
||||
import { useState, useCallback } from 'react'
|
||||
import { Head } from '@inertiajs/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import i18n from 'i18next'
|
||||
import SettingsLayout from '~/layouts/SettingsLayout'
|
||||
import { SystemInformationResponse } from '../../../types/system'
|
||||
import { formatBytes } from '~/lib/util'
|
||||
|
|
@ -19,6 +21,8 @@ import { IconCpu, IconDatabase, IconServer, IconDeviceDesktop, IconComponents }
|
|||
export default function SettingsPage(props: {
|
||||
system: { info: SystemInformationResponse | undefined }
|
||||
}) {
|
||||
const { t } = useTranslation('settings')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const { data: info } = useSystemInfo({
|
||||
initialData: props.system.info,
|
||||
})
|
||||
|
|
@ -44,7 +48,7 @@ export default function SettingsPage(props: {
|
|||
const handleForceReinstallOllama = () => {
|
||||
openModal(
|
||||
<StyledModal
|
||||
title="Reinstall AI Assistant?"
|
||||
title={tCommon('alerts.reinstallAIConfirmTitle')}
|
||||
onConfirm={async () => {
|
||||
closeAllModals()
|
||||
setReinstalling(true)
|
||||
|
|
@ -54,14 +58,14 @@ export default function SettingsPage(props: {
|
|||
throw new Error(response?.message || 'Force reinstall failed')
|
||||
}
|
||||
addNotification({
|
||||
message: 'AI Assistant is being reinstalled with GPU support. This page will reload shortly.',
|
||||
message: tCommon('alerts.reinstallSuccess'),
|
||||
type: 'success',
|
||||
})
|
||||
try { localStorage.removeItem('nomad:gpu-banner-dismissed') } catch {}
|
||||
setTimeout(() => window.location.reload(), 5000)
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: `Failed to reinstall: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
message: tCommon('alerts.reinstallFailed', { error: error instanceof Error ? error.message : 'Unknown error' }),
|
||||
type: 'error',
|
||||
})
|
||||
setReinstalling(false)
|
||||
|
|
@ -69,13 +73,11 @@ export default function SettingsPage(props: {
|
|||
}}
|
||||
onCancel={closeAllModals}
|
||||
open={true}
|
||||
confirmText="Reinstall"
|
||||
cancelText="Cancel"
|
||||
confirmText={tCommon('buttons.reinstall')}
|
||||
cancelText={tCommon('buttons.cancel')}
|
||||
>
|
||||
<p className="text-text-primary">
|
||||
This will recreate the AI Assistant container with GPU support enabled.
|
||||
Your downloaded models will be preserved. The service will be briefly
|
||||
unavailable during reinstall.
|
||||
{tCommon('alerts.reinstallAIConfirmMessage')}
|
||||
</p>
|
||||
</StyledModal>,
|
||||
'gpu-health-force-reinstall-modal'
|
||||
|
|
@ -110,22 +112,22 @@ export default function SettingsPage(props: {
|
|||
|
||||
return (
|
||||
<SettingsLayout>
|
||||
<Head title="System Information" />
|
||||
<Head title={t('system.title')} />
|
||||
<div className="xl:pl-72 w-full">
|
||||
<main className="px-6 lg:px-12 py-6 lg:py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-desert-green mb-2">System Information</h1>
|
||||
<h1 className="text-4xl font-bold text-desert-green mb-2">{t('system.title')}</h1>
|
||||
<p className="text-desert-stone-dark">
|
||||
Real-time monitoring and diagnostics • Last updated: {new Date().toLocaleString()} •
|
||||
Refreshing data every 30 seconds
|
||||
{t('system.subtitle')} • {t('system.lastUpdated', { time: new Date().toLocaleString() })} •
|
||||
{' '}{t('system.refreshing')}
|
||||
</p>
|
||||
</div>
|
||||
{Number(memoryUsagePercent) > 90 && (
|
||||
<div className="mb-6">
|
||||
<Alert
|
||||
type="error"
|
||||
title="Very High Memory Usage Detected"
|
||||
message="System memory usage exceeds 90%. Performance degradation may occur."
|
||||
title={tCommon('alerts.highMemoryTitle')}
|
||||
message={tCommon('alerts.highMemoryMessage')}
|
||||
variant="bordered"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -133,24 +135,24 @@ export default function SettingsPage(props: {
|
|||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||
<div className="w-1 h-6 bg-desert-green" />
|
||||
Resource Usage
|
||||
{t('sections.resourceUsage')}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||
<CircularGauge
|
||||
value={info?.currentLoad.currentLoad || 0}
|
||||
label="CPU Usage"
|
||||
label={t('labels.cpuUsage')}
|
||||
size="lg"
|
||||
variant="cpu"
|
||||
subtext={`${info?.cpu.cores || 0} cores`}
|
||||
subtext={t('labels.cores', { count: info?.cpu.cores || 0 })}
|
||||
icon={<IconCpu className="w-8 h-8" />}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||
<CircularGauge
|
||||
value={Number(memoryUsagePercent)}
|
||||
label="Memory Usage"
|
||||
label={t('labels.memoryUsage')}
|
||||
size="lg"
|
||||
variant="memory"
|
||||
subtext={`${formatBytes(memoryUsed)} / ${formatBytes(info?.mem.total || 0)}`}
|
||||
|
|
@ -160,7 +162,7 @@ export default function SettingsPage(props: {
|
|||
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||
<CircularGauge
|
||||
value={Number(swapUsagePercent)}
|
||||
label="Swap Usage"
|
||||
label={t('labels.swapUsage')}
|
||||
size="lg"
|
||||
variant="disk"
|
||||
subtext={`${formatBytes(info?.mem.swapused || 0)} / ${formatBytes(info?.mem.swaptotal || 0)}`}
|
||||
|
|
@ -172,7 +174,7 @@ export default function SettingsPage(props: {
|
|||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||
<div className="w-1 h-6 bg-desert-green" />
|
||||
System Details
|
||||
{t('sections.systemDetails')}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
|
@ -181,11 +183,11 @@ export default function SettingsPage(props: {
|
|||
icon={<IconDeviceDesktop className="w-6 h-6" />}
|
||||
variant="elevated"
|
||||
data={[
|
||||
{ label: 'Distribution', value: info?.os.distro },
|
||||
{ label: 'Kernel Version', value: info?.os.kernel },
|
||||
{ label: 'Architecture', value: info?.os.arch },
|
||||
{ label: 'Hostname', value: info?.os.hostname },
|
||||
{ label: 'Platform', value: info?.os.platform },
|
||||
{ label: t('os.distribution'), value: info?.os.distro },
|
||||
{ label: t('os.kernelVersion'), value: info?.os.kernel },
|
||||
{ label: t('os.architecture'), value: info?.os.arch },
|
||||
{ label: t('os.hostname'), value: info?.os.hostname },
|
||||
{ label: t('os.platform'), value: info?.os.platform },
|
||||
]}
|
||||
/>
|
||||
<InfoCard
|
||||
|
|
@ -193,13 +195,13 @@ export default function SettingsPage(props: {
|
|||
icon={<IconCpu className="w-6 h-6" />}
|
||||
variant="elevated"
|
||||
data={[
|
||||
{ label: 'Manufacturer', value: info?.cpu.manufacturer },
|
||||
{ label: 'Brand', value: info?.cpu.brand },
|
||||
{ label: 'Cores', value: info?.cpu.cores },
|
||||
{ label: 'Physical Cores', value: info?.cpu.physicalCores },
|
||||
{ label: t('cpu.manufacturer'), value: info?.cpu.manufacturer },
|
||||
{ label: t('cpu.brand'), value: info?.cpu.brand },
|
||||
{ label: t('cpu.cores'), value: info?.cpu.cores },
|
||||
{ label: t('cpu.physicalCores'), value: info?.cpu.physicalCores },
|
||||
{
|
||||
label: 'Virtualization',
|
||||
value: info?.cpu.virtualization ? 'Enabled' : 'Disabled',
|
||||
label: t('cpu.virtualization'),
|
||||
value: info?.cpu.virtualization ? t('cpu.enabled') : t('cpu.disabled'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
|
@ -208,12 +210,12 @@ export default function SettingsPage(props: {
|
|||
<Alert
|
||||
type="warning"
|
||||
variant="bordered"
|
||||
title="GPU Not Accessible to AI Assistant"
|
||||
message="Your system has an NVIDIA GPU, but the AI Assistant can't access it. AI is running on CPU only, which is significantly slower."
|
||||
title={tCommon('alerts.gpuNotAccessibleTitle')}
|
||||
message={tCommon('alerts.gpuNotAccessibleMessage')}
|
||||
dismissible={true}
|
||||
onDismiss={handleDismissGpuBanner}
|
||||
buttonProps={{
|
||||
children: 'Fix: Reinstall AI Assistant',
|
||||
children: tCommon('alerts.fixReinstallAI'),
|
||||
icon: 'IconRefresh',
|
||||
variant: 'action',
|
||||
size: 'sm',
|
||||
|
|
@ -226,15 +228,15 @@ export default function SettingsPage(props: {
|
|||
)}
|
||||
{info?.graphics?.controllers && info.graphics.controllers.length > 0 && (
|
||||
<InfoCard
|
||||
title="Graphics"
|
||||
title={t('gpu.title')}
|
||||
icon={<IconComponents className="w-6 h-6" />}
|
||||
variant="elevated"
|
||||
data={info.graphics.controllers.map((gpu, i) => {
|
||||
const prefix = info.graphics.controllers.length > 1 ? `GPU ${i + 1} ` : ''
|
||||
return [
|
||||
{ label: `${prefix}Model`, value: gpu.model },
|
||||
{ label: `${prefix}Vendor`, value: gpu.vendor },
|
||||
{ label: `${prefix}VRAM`, value: gpu.vram ? `${gpu.vram} MB` : 'N/A' },
|
||||
{ label: `${prefix}${t('gpu.model')}`, value: gpu.model },
|
||||
{ label: `${prefix}${t('gpu.vendor')}`, value: gpu.vendor },
|
||||
{ label: `${prefix}${t('gpu.vram')}`, value: gpu.vram ? `${gpu.vram} MB` : 'N/A' },
|
||||
]
|
||||
}).flat()}
|
||||
/>
|
||||
|
|
@ -244,7 +246,7 @@ export default function SettingsPage(props: {
|
|||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||
<div className="w-1 h-6 bg-desert-green" />
|
||||
Memory Allocation
|
||||
{t('sections.memoryAllocation')}
|
||||
</h2>
|
||||
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
||||
|
|
@ -253,7 +255,7 @@ export default function SettingsPage(props: {
|
|||
{formatBytes(info?.mem.total || 0)}
|
||||
</div>
|
||||
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
||||
Total RAM
|
||||
{t('labels.totalRam')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
|
|
@ -261,7 +263,7 @@ export default function SettingsPage(props: {
|
|||
{formatBytes(memoryUsed)}
|
||||
</div>
|
||||
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
||||
Used RAM
|
||||
{t('labels.usedRam')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
|
|
@ -269,7 +271,7 @@ export default function SettingsPage(props: {
|
|||
{formatBytes(info?.mem.available || 0)}
|
||||
</div>
|
||||
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
||||
Available RAM
|
||||
{t('labels.availableRam')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -280,7 +282,7 @@ export default function SettingsPage(props: {
|
|||
></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-sm font-bold text-white drop-shadow-md z-10">
|
||||
{memoryUsagePercent}% Utilized
|
||||
{t('labels.utilized', { percent: memoryUsagePercent })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -289,7 +291,7 @@ export default function SettingsPage(props: {
|
|||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||
<div className="w-1 h-6 bg-desert-green" />
|
||||
Storage Devices
|
||||
{t('sections.storageDevices')}
|
||||
</h2>
|
||||
|
||||
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||
|
|
@ -299,17 +301,17 @@ export default function SettingsPage(props: {
|
|||
progressiveBarColor={true}
|
||||
statuses={[
|
||||
{
|
||||
label: 'Normal',
|
||||
label: t('storage.normal'),
|
||||
min_threshold: 0,
|
||||
color_class: 'bg-desert-olive',
|
||||
},
|
||||
{
|
||||
label: 'Warning - Usage High',
|
||||
label: t('storage.warningHigh'),
|
||||
min_threshold: 75,
|
||||
color_class: 'bg-desert-orange',
|
||||
},
|
||||
{
|
||||
label: 'Critical - Disk Almost Full',
|
||||
label: t('storage.criticalFull'),
|
||||
min_threshold: 90,
|
||||
color_class: 'bg-desert-red',
|
||||
},
|
||||
|
|
@ -317,7 +319,7 @@ export default function SettingsPage(props: {
|
|||
/>
|
||||
) : (
|
||||
<div className="text-center text-desert-stone-dark py-8">
|
||||
No storage devices detected
|
||||
{t('labels.noStorageDetected')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -325,12 +327,38 @@ export default function SettingsPage(props: {
|
|||
<section>
|
||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||
<div className="w-1 h-6 bg-desert-green" />
|
||||
System Status
|
||||
{t('sections.systemStatus')}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<StatusCard title="System Uptime" value={uptimeDisplay} />
|
||||
<StatusCard title="CPU Cores" value={info?.cpu.cores || 0} />
|
||||
<StatusCard title="Storage Devices" value={storageItems.length} />
|
||||
<StatusCard title={t('labels.systemUptime')} value={uptimeDisplay} />
|
||||
<StatusCard title={t('labels.cpuCores')} value={info?.cpu.cores || 0} />
|
||||
<StatusCard title={t('labels.storageDevicesCount')} value={storageItems.length} />
|
||||
</div>
|
||||
</section>
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||
<div className="w-1 h-6 bg-desert-green" />
|
||||
{t('sections.preferences')}
|
||||
</h2>
|
||||
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-text-primary">{tCommon('language.label')}</h3>
|
||||
</div>
|
||||
<select
|
||||
value={i18n.language}
|
||||
onChange={(e) => {
|
||||
const lang = e.target.value
|
||||
i18n.changeLanguage(lang)
|
||||
try { localStorage.setItem('nomad:language', lang) } catch {}
|
||||
api.updateSetting('ui.language', lang).catch(() => {})
|
||||
}}
|
||||
className="bg-surface-primary border border-border-default rounded-lg px-4 py-2 text-text-primary focus:outline-none focus:ring-2 focus:ring-desert-green"
|
||||
>
|
||||
<option value="en">{tCommon('language.en')}</option>
|
||||
<option value="pt-BR">{tCommon('language.pt-BR')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
87
admin/package-lock.json
generated
87
admin/package-lock.json
generated
|
|
@ -46,6 +46,7 @@
|
|||
"edge.js": "^6.2.1",
|
||||
"fast-xml-parser": "^5.5.6",
|
||||
"fuse.js": "^7.1.0",
|
||||
"i18next": "^25.10.5",
|
||||
"luxon": "^3.6.1",
|
||||
"maplibre-gl": "^4.7.1",
|
||||
"mysql2": "^3.14.1",
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
"react": "^19.1.0",
|
||||
"react-adonis-transmit": "^1.0.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^16.6.2",
|
||||
"react-map-gl": "^8.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
|
@ -1092,6 +1094,15 @@
|
|||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
|
|
@ -9646,6 +9657,15 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
|
|
@ -9749,6 +9769,37 @@
|
|||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.10.5",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.10.5.tgz",
|
||||
"integrity": "sha512-jRnF7eRNsdcnh7AASSgaU3lj/8lJZuHkfsouetnLEDH0xxE1vVi7qhiJ9RhdSPUyzg4ltb7P7aXsFlTk9sxL2w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.locize.com/i18next"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.locize.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
|
|
@ -13776,6 +13827,33 @@
|
|||
"react": "^19.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "16.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.6.2.tgz",
|
||||
"integrity": "sha512-/S/GPzElTqEi5o2kzd0/O2627hPDmE6OGhJCCwCfUaQ3syyu+kaYH8/PYFtZeWc25NzfxTN/2fD1QjvrTgrFfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"html-parse-stringify": "^3.0.1",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 25.6.2",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
|
|
@ -16234,6 +16312,15 @@
|
|||
"vite": "^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vt-pbf": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@
|
|||
"edge.js": "^6.2.1",
|
||||
"fast-xml-parser": "^5.5.6",
|
||||
"fuse.js": "^7.1.0",
|
||||
"i18next": "^25.10.5",
|
||||
"luxon": "^3.6.1",
|
||||
"maplibre-gl": "^4.7.1",
|
||||
"mysql2": "^3.14.1",
|
||||
|
|
@ -110,6 +111,7 @@
|
|||
"react": "^19.1.0",
|
||||
"react-adonis-transmit": "^1.0.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^16.6.2",
|
||||
"react-map-gl": "^8.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user