From be298d135ffce0d771106c0e32039050f50199ab Mon Sep 17 00:00:00 2001 From: Isaac S Date: Thu, 28 May 2026 18:24:28 -0400 Subject: [PATCH 1/2] Add i18n with language selector (12 languages, cookie-based) Adds internationalization to the v5 app using next-intl in the "App Router without i18n routing" (cookie-based) configuration, so existing routes and links stay unchanged. - 12 languages: en, zh-CN, es, hi, ko, ar (RTL), fr, pt-BR, ru, tr, ja, he (RTL). English authored by hand; the other 11 are machine-translated and want a native-speaker QC pass. - Locale stored in NEXT_LOCALE cookie (default en); first visit detected from Accept-Language. - corrected before paint via an inline script; dir="rtl" for ar/he with logical-property + minimal RTL CSS so the chat FAB/sheet don't break. - in the top nav lists each language by endonym, sets the cookie via a Server Action, and router.refresh()es so Server Components re-render in the new locale. - All UI chrome translated: nav, status strip, gallery, tool detail headings, projects/about copy, and the full chat widget. - Locale-aware AI chat: ChatFab sends the active locale; route.ts reads it and adds one system-prompt instruction to reply in the user's language while keeping tool/unit names and maintenance ticket title/description in English for staff. Notion-sourced data (tool names, descriptions) and PDF content are intentionally not translated. Co-Authored-By: Claude Opus 4.8 (1M context) --- v5/messages/ar.json | 124 +++++ v5/messages/en.json | 124 +++++ v5/messages/es.json | 124 +++++ v5/messages/fr.json | 124 +++++ v5/messages/he.json | 124 +++++ v5/messages/hi.json | 124 +++++ v5/messages/ja.json | 124 +++++ v5/messages/ko.json | 124 +++++ v5/messages/pt-BR.json | 124 +++++ v5/messages/ru.json | 124 +++++ v5/messages/tr.json | 124 +++++ v5/messages/zh-CN.json | 124 +++++ v5/next.config.ts | 5 +- v5/package-lock.json | 710 ++++++++++++++++++++++++- v5/package.json | 1 + v5/src/app/about/page.tsx | 53 +- v5/src/app/api/chat/route.ts | 16 +- v5/src/app/layout.tsx | 31 +- v5/src/app/projects/page.tsx | 15 +- v5/src/components/ChatFab.tsx | 111 ++-- v5/src/components/DetailShell.tsx | 84 +-- v5/src/components/GalleryFallback.tsx | 9 +- v5/src/components/GalleryShell.tsx | 48 +- v5/src/components/GlobalChrome.tsx | 15 +- v5/src/components/LanguageSelector.tsx | 46 ++ v5/src/components/LocaleHtmlScript.tsx | 18 + v5/src/components/PrimaryNav.tsx | 14 +- v5/src/components/ThemeToggle.tsx | 8 +- v5/src/i18n/actions.ts | 13 + v5/src/i18n/config.ts | 52 ++ v5/src/i18n/locale.ts | 74 +++ v5/src/i18n/request.ts | 11 + v5/src/styles/globals.css | 93 ++++ 33 files changed, 2732 insertions(+), 183 deletions(-) create mode 100644 v5/messages/ar.json create mode 100644 v5/messages/en.json create mode 100644 v5/messages/es.json create mode 100644 v5/messages/fr.json create mode 100644 v5/messages/he.json create mode 100644 v5/messages/hi.json create mode 100644 v5/messages/ja.json create mode 100644 v5/messages/ko.json create mode 100644 v5/messages/pt-BR.json create mode 100644 v5/messages/ru.json create mode 100644 v5/messages/tr.json create mode 100644 v5/messages/zh-CN.json create mode 100644 v5/src/components/LanguageSelector.tsx create mode 100644 v5/src/components/LocaleHtmlScript.tsx create mode 100644 v5/src/i18n/actions.ts create mode 100644 v5/src/i18n/config.ts create mode 100644 v5/src/i18n/locale.ts create mode 100644 v5/src/i18n/request.ts diff --git a/v5/messages/ar.json b/v5/messages/ar.json new file mode 100644 index 0000000..d170708 --- /dev/null +++ b/v5/messages/ar.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "الأدوات", + "projects": "المشاريع", + "about": "حول", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "التنقّل الرئيسي", + "utilityControlsLabel": "عناصر التحكم المساعدة", + "themeToggleAria": "تبديل سمة الألوان (النظام ← فاتح ← داكن)", + "themeToggleTitle": "تبديل سمة الألوان", + "languageLabel": "اللغة", + "languageSelectorAria": "اختيار اللغة" + }, + "status": { + "labStatusLabel": "حالة المختبر", + "toolsInInventory": "{count} أداة في المخزون" + }, + "gallery": { + "title": "الأدوات // الآلات", + "searchPlaceholder": "ابحث في المخزون...", + "searchAria": "البحث في المخزون", + "category": "الفئة:", + "training": "التدريب:", + "viewModeLabel": "وضع العرض", + "grid": "[ شبكة ]", + "table": "[ جدول ]", + "toolGalleryLabel": "معرض الأدوات", + "empty": "لم يتم العثور على أدوات مطابقة.", + "loading": "جارٍ تحميل المخزون", + "columnTool": "الأداة", + "columnCategory": "الفئة", + "columnZone": "المنطقة", + "columnTraining": "التدريب", + "trainingBeginner": "مبتدئ", + "trainingIntermediate": "متوسط", + "trainingAdvanced": "متقدم" + }, + "detail": { + "breadcrumbTools": "الأدوات", + "breadcrumbInventory": "المخزون", + "toolStatusLabel": "حالة الأداة", + "trainingChip": "تدريب {level}", + "ppeRequired": "معدات الوقاية الشخصية مطلوبة", + "viewSafetyDoc": "عرض وثيقة السلامة", + "viewSop": "عرض إجراء التشغيل (SOP)", + "atAGlanceLabel": "نظرة سريعة", + "materials": "المواد", + "contactStaff": "تواصل مع فريق MakerLab", + "safetyAccess": "السلامة والوصول", + "ppeNotice": "يجب ارتداء معدات الوقاية الشخصية قبل الاستخدام. راجع الإرشادات المعلَّقة.", + "emergencyStop": "إيقاف الطوارئ", + "emergencyStopFallback": "اتبع إرشادات المختبر المعلَّقة وأبلغ الفريق في حالات الطوارئ.", + "useRestrictions": "قيود الاستخدام", + "useRestrictionsFallback": "متاح لجميع مستخدمي MakerLab المدرَّبين خلال ساعات عمل المختبر.", + "documentsResources": "المستندات والموارد", + "openResource": "فتح المورد", + "resourceFallback": "مورد", + "noDocuments": "لا توجد مستندات مرتبطة بعد.", + "details": "التفاصيل", + "category": "الفئة", + "location": "الموقع", + "trainingRow": "التدريب", + "mapId": "معرّف الخريطة", + "tags": "الوسوم", + "notes": "ملاحظات", + "physicalMachines": "الآلات الفعلية", + "unit": "الوحدة", + "status": "الحالة", + "condition": "الوضع", + "serial": "الرقم التسلسلي", + "acquired": "تاريخ الاقتناء", + "notesTips": "ملاحظات ونصائح", + "backToTools": "‹ العودة إلى جميع الأدوات" + }, + "projects": { + "eyebrow": "المشاريع", + "title": "قريبًا", + "body": "يجري إعداد معرض لمشاريع MakerLab — أعمال الطلاب ونتائج المقررات ومعروضات منتقاة من الفريق. في الوقت الحالي، تصفّح كتالوج الأدوات لمعرفة ما هو متاح في المختبر.", + "browseTools": "تصفّح الأدوات" + }, + "about": { + "eyebrow": "حول", + "title": "كتالوج بقيادة الطلاب لمختبر Cornell Tech MakerLab.", + "intro": "MakerLab Tools هو نظام رقمي للمخزون والاستكشاف لمختبر Cornell Tech MakerLab. يساعد الطلاب على تصفّح المعدات والعثور على وثائق السلامة ومعرفة الآلات الفعلية المتاحة في المختبر قبل الذهاب إليه.", + "origin": "بدأ المشروع كنموذج أولي في عطلة نهاية الأسبوع ثم تطوّر إلى الكتالوج الحالي المعتمد على Notion. تُخزَّن الأدوات والوحدات والمواقع والموارد جميعها في Notion وتُغذّي الموقع عبر طبقة API للقراءة فقط مع تخزين مؤقت — بحيث يمكن للفريق والطلاب المتطوعين إدارة المحتوى دون لمس الكود.", + "builtByHeading": "من قام بإنشائه", + "builtByBody": "Isaac Steinberg، Johnson Cornell Tech MBA '26. تم إنشاؤه كمشروع بقيادة الطلاب لتسهيل عثور مجتمع MakerLab على معدات المختبر واستخدامها بأمان.", + "howItWorksHeading": "كيف يعمل", + "howItWorksBody": "الواجهة الأمامية هي تطبيق Next.js 16 مُنشَر على Vercel. تعمل واجهة Notion API كمصدر للحقيقة بالنسبة للكتالوج، مع طبقة تخزين مؤقت لكل طلب لإبقاء الصفحة سريعة تحت الضغط. تُنسَخ صور الأدوات محليًا لتظهر بشكل موثوق حتى عند انتهاء صلاحية روابط URL الموقّعة من المصدر.", + "feedbackBody": "إذا لاحظت خطأ أو كان لديك اقتراح، تحدّث مع فريق MakerLab — تتم مراجعة التصحيحات المُعلَّمة وتحديثها في Notion.", + "browseTools": "تصفّح الأدوات" + }, + "chat": { + "openAria": "فتح مساعد MakerLab", + "title": "مساعد MAKERLAB", + "newChatAria": "بدء محادثة جديدة", + "newChatTitle": "بدء محادثة جديدة", + "closeAria": "إغلاق المساعد", + "closeTitle": "إغلاق (يحتفظ بالمحادثة)", + "closeScrimAria": "إغلاق مساعد MakerLab", + "greetingTool": "اسأل عن هذه الأداة — لديّ مواصفاتها وموادها وروابط مواردها.", + "greetingGeneral": "كيف يمكنني مساعدتك اليوم؟", + "suggestionFindMachine": "العثور على آلة مناسبة لمشروع", + "suggestionTraining": "التحقق من متطلبات التدريب", + "suggestionSafety": "السؤال عن السلامة أو السياسات", + "composerPlaceholder": "اسأل وحدة تحكّم المختبر...", + "composerAria": "اسأل وحدة تحكّم المختبر", + "sendAria": "إرسال", + "attachAria": "إرفاق صور", + "attachTitle": "إرفاق صور", + "removePhotoAria": "إزالة {name}", + "uploadingAria": "جارٍ رفع الصورة", + "onlyImages": "يُسمح بملفات الصور فقط.", + "uploadFailed": "فشل رفع الصورة", + "toolRunningAria": "الأداة قيد التشغيل", + "typingAria": "المساعد يكتب", + "readingManualsAria": "جارٍ قراءة الأدلة", + "reading": "جارٍ القراءة: {titles}…", + "error": "حدث خطأ ما. حاول مرة أخرى.", + "lookingUpUnit": "🔍 جارٍ البحث عن تفاصيل الوحدة…", + "filingTicket": "📝 جارٍ تسجيل تذكرة الصيانة…", + "working": "جارٍ العمل على ذلك…" + } +} diff --git a/v5/messages/en.json b/v5/messages/en.json new file mode 100644 index 0000000..ff3be98 --- /dev/null +++ b/v5/messages/en.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "TOOLS", + "projects": "PROJECTS", + "about": "ABOUT", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "Primary navigation", + "utilityControlsLabel": "Utility controls", + "themeToggleAria": "Cycle color theme (system → light → dark)", + "themeToggleTitle": "Cycle color theme", + "languageLabel": "Language", + "languageSelectorAria": "Select language" + }, + "status": { + "labStatusLabel": "Lab status", + "toolsInInventory": "{count} TOOLS IN INVENTORY" + }, + "gallery": { + "title": "TOOLS // MACHINES", + "searchPlaceholder": "search inventory...", + "searchAria": "Search inventory", + "category": "CATEGORY:", + "training": "TRAINING:", + "viewModeLabel": "View mode", + "grid": "[ GRID ]", + "table": "[ TABLE ]", + "toolGalleryLabel": "Tool gallery", + "empty": "No matching tools found.", + "loading": "loading inventory", + "columnTool": "Tool", + "columnCategory": "Category", + "columnZone": "Zone", + "columnTraining": "Training", + "trainingBeginner": "Beginner", + "trainingIntermediate": "Intermediate", + "trainingAdvanced": "Advanced" + }, + "detail": { + "breadcrumbTools": "Tools", + "breadcrumbInventory": "Inventory", + "toolStatusLabel": "Tool status", + "trainingChip": "{level} training", + "ppeRequired": "PPE Required", + "viewSafetyDoc": "View Safety Doc", + "viewSop": "View SOP", + "atAGlanceLabel": "At a glance", + "materials": "Materials", + "contactStaff": "Contact MakerLab staff", + "safetyAccess": "Safety & Access", + "ppeNotice": "PPE must be worn before use. Review posted guidelines.", + "emergencyStop": "Emergency Stop", + "emergencyStopFallback": "Follow posted lab guidance and notify staff in an emergency.", + "useRestrictions": "Use Restrictions", + "useRestrictionsFallback": "Open to all trained MakerLab users during lab hours.", + "documentsResources": "Documents & Resources", + "openResource": "Open resource", + "resourceFallback": "Resource", + "noDocuments": "No documents linked yet.", + "details": "Details", + "category": "Category", + "location": "Location", + "trainingRow": "Training", + "mapId": "Map ID", + "tags": "Tags", + "notes": "Notes", + "physicalMachines": "Physical Machines", + "unit": "Unit", + "status": "Status", + "condition": "Condition", + "serial": "Serial", + "acquired": "Acquired", + "notesTips": "Notes & Tips", + "backToTools": "‹ Back to all tools" + }, + "projects": { + "eyebrow": "Projects", + "title": "Coming soon", + "body": "A gallery of MakerLab projects is in the works — student builds, course outcomes, and staff-curated showcases. For now, browse the tool catalog to see what's available in the lab.", + "browseTools": "Browse tools" + }, + "about": { + "eyebrow": "About", + "title": "A student-led catalog for the Cornell Tech MakerLab.", + "intro": "MakerLab Tools is a digital inventory and discovery system for the Cornell Tech MakerLab. It helps students browse equipment, find safety documentation, and identify which physical machines are available in the lab before they walk over.", + "origin": "The project began as a weekend prototype and grew into the current Notion-backed catalog. Tools, units, locations, and resources all live in Notion and feed the site through a cached read-only API layer — so staff and student volunteers can curate content without touching code.", + "builtByHeading": "Built by", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. Built as a student-led project to make it easier for the MakerLab community to find and safely use the lab's equipment.", + "howItWorksHeading": "How it works", + "howItWorksBody": "The front end is a Next.js 16 app deployed on Vercel. The Notion API serves as the source of truth for the catalog, with a per-request cache layer so the page stays fast under load. Tool images are mirrored locally so they render reliably even when upstream signed URLs expire.", + "feedbackBody": "If you spot a mistake or have a suggestion, talk to the MakerLab staff — flagged corrections are reviewed and updated in Notion.", + "browseTools": "Browse tools" + }, + "chat": { + "openAria": "Open MakerLab assistant", + "title": "MAKERLAB ASSISTANT", + "newChatAria": "Start new chat", + "newChatTitle": "Start new chat", + "closeAria": "Close assistant", + "closeTitle": "Close (keeps conversation)", + "closeScrimAria": "Close MakerLab assistant", + "greetingTool": "Ask about this tool — I have its specs, materials, and resource links.", + "greetingGeneral": "How can I help you today?", + "suggestionFindMachine": "Find a machine for a project", + "suggestionTraining": "Check training requirements", + "suggestionSafety": "Ask about safety or policy", + "composerPlaceholder": "Ask the lab console...", + "composerAria": "Ask the lab console", + "sendAria": "Send", + "attachAria": "Attach photos", + "attachTitle": "Attach photos", + "removePhotoAria": "Remove {name}", + "uploadingAria": "Uploading photo", + "onlyImages": "Only image files are supported.", + "uploadFailed": "Photo upload failed", + "toolRunningAria": "Tool running", + "typingAria": "Assistant is typing", + "readingManualsAria": "Reading manuals", + "reading": "Reading: {titles}…", + "error": "Something went wrong. Try again.", + "lookingUpUnit": "🔍 Looking up unit details…", + "filingTicket": "📝 Filing maintenance ticket…", + "working": "Working on it…" + } +} diff --git a/v5/messages/es.json b/v5/messages/es.json new file mode 100644 index 0000000..3171a72 --- /dev/null +++ b/v5/messages/es.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "HERRAMIENTAS", + "projects": "PROYECTOS", + "about": "ACERCA DE", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "Navegación principal", + "utilityControlsLabel": "Controles de utilidad", + "themeToggleAria": "Alternar tema de color (sistema → claro → oscuro)", + "themeToggleTitle": "Alternar tema de color", + "languageLabel": "Idioma", + "languageSelectorAria": "Seleccionar idioma" + }, + "status": { + "labStatusLabel": "Estado del laboratorio", + "toolsInInventory": "{count} HERRAMIENTAS EN INVENTARIO" + }, + "gallery": { + "title": "HERRAMIENTAS // MÁQUINAS", + "searchPlaceholder": "buscar en el inventario...", + "searchAria": "Buscar en el inventario", + "category": "CATEGORÍA:", + "training": "FORMACIÓN:", + "viewModeLabel": "Modo de vista", + "grid": "[ CUADRÍCULA ]", + "table": "[ TABLA ]", + "toolGalleryLabel": "Galería de herramientas", + "empty": "No se encontraron herramientas coincidentes.", + "loading": "cargando inventario", + "columnTool": "Herramienta", + "columnCategory": "Categoría", + "columnZone": "Zona", + "columnTraining": "Formación", + "trainingBeginner": "Principiante", + "trainingIntermediate": "Intermedio", + "trainingAdvanced": "Avanzado" + }, + "detail": { + "breadcrumbTools": "Herramientas", + "breadcrumbInventory": "Inventario", + "toolStatusLabel": "Estado de la herramienta", + "trainingChip": "formación {level}", + "ppeRequired": "EPP obligatorio", + "viewSafetyDoc": "Ver documento de seguridad", + "viewSop": "Ver procedimiento (SOP)", + "atAGlanceLabel": "De un vistazo", + "materials": "Materiales", + "contactStaff": "Contactar al personal del MakerLab", + "safetyAccess": "Seguridad y acceso", + "ppeNotice": "Se debe usar el EPP antes de utilizar el equipo. Consulta las pautas publicadas.", + "emergencyStop": "Parada de emergencia", + "emergencyStopFallback": "Sigue las indicaciones del laboratorio y avisa al personal en caso de emergencia.", + "useRestrictions": "Restricciones de uso", + "useRestrictionsFallback": "Abierto a todos los usuarios capacitados del MakerLab durante el horario del laboratorio.", + "documentsResources": "Documentos y recursos", + "openResource": "Abrir recurso", + "resourceFallback": "Recurso", + "noDocuments": "Aún no hay documentos vinculados.", + "details": "Detalles", + "category": "Categoría", + "location": "Ubicación", + "trainingRow": "Formación", + "mapId": "ID de mapa", + "tags": "Etiquetas", + "notes": "Notas", + "physicalMachines": "Máquinas físicas", + "unit": "Unidad", + "status": "Estado", + "condition": "Condición", + "serial": "Número de serie", + "acquired": "Adquirido", + "notesTips": "Notas y consejos", + "backToTools": "‹ Volver a todas las herramientas" + }, + "projects": { + "eyebrow": "Proyectos", + "title": "Próximamente", + "body": "Una galería de proyectos del MakerLab está en marcha: creaciones de estudiantes, resultados de cursos y muestras seleccionadas por el personal. Por ahora, explora el catálogo de herramientas para ver lo que hay disponible en el laboratorio.", + "browseTools": "Explorar herramientas" + }, + "about": { + "eyebrow": "Acerca de", + "title": "Un catálogo dirigido por estudiantes para el Cornell Tech MakerLab.", + "intro": "MakerLab Tools es un sistema digital de inventario y descubrimiento para el Cornell Tech MakerLab. Ayuda a los estudiantes a explorar el equipo, encontrar documentación de seguridad e identificar qué máquinas físicas están disponibles en el laboratorio antes de acudir.", + "origin": "El proyecto comenzó como un prototipo de fin de semana y creció hasta convertirse en el catálogo actual respaldado por Notion. Las herramientas, unidades, ubicaciones y recursos viven en Notion y alimentan el sitio a través de una capa de API de solo lectura con caché, de modo que el personal y los estudiantes voluntarios pueden gestionar el contenido sin tocar el código.", + "builtByHeading": "Creado por", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. Construido como un proyecto dirigido por estudiantes para facilitar que la comunidad del MakerLab encuentre y use de forma segura el equipo del laboratorio.", + "howItWorksHeading": "Cómo funciona", + "howItWorksBody": "El front end es una aplicación Next.js 16 desplegada en Vercel. La API de Notion sirve como fuente de verdad para el catálogo, con una capa de caché por solicitud para que la página se mantenga rápida bajo carga. Las imágenes de las herramientas se replican localmente para que se muestren de forma fiable incluso cuando expiran las URL firmadas de origen.", + "feedbackBody": "Si detectas un error o tienes una sugerencia, habla con el personal del MakerLab: las correcciones marcadas se revisan y se actualizan en Notion.", + "browseTools": "Explorar herramientas" + }, + "chat": { + "openAria": "Abrir el asistente del MakerLab", + "title": "ASISTENTE MAKERLAB", + "newChatAria": "Iniciar nuevo chat", + "newChatTitle": "Iniciar nuevo chat", + "closeAria": "Cerrar asistente", + "closeTitle": "Cerrar (conserva la conversación)", + "closeScrimAria": "Cerrar el asistente del MakerLab", + "greetingTool": "Pregúntame sobre esta herramienta: tengo sus especificaciones, materiales y enlaces a recursos.", + "greetingGeneral": "¿En qué puedo ayudarte hoy?", + "suggestionFindMachine": "Encontrar una máquina para un proyecto", + "suggestionTraining": "Consultar requisitos de formación", + "suggestionSafety": "Preguntar sobre seguridad o normas", + "composerPlaceholder": "Pregunta a la consola del laboratorio...", + "composerAria": "Pregunta a la consola del laboratorio", + "sendAria": "Enviar", + "attachAria": "Adjuntar fotos", + "attachTitle": "Adjuntar fotos", + "removePhotoAria": "Quitar {name}", + "uploadingAria": "Subiendo foto", + "onlyImages": "Solo se admiten archivos de imagen.", + "uploadFailed": "Error al subir la foto", + "toolRunningAria": "Herramienta en ejecución", + "typingAria": "El asistente está escribiendo", + "readingManualsAria": "Leyendo manuales", + "reading": "Leyendo: {titles}…", + "error": "Algo salió mal. Inténtalo de nuevo.", + "lookingUpUnit": "🔍 Buscando detalles de la unidad…", + "filingTicket": "📝 Registrando ticket de mantenimiento…", + "working": "Trabajando en ello…" + } +} diff --git a/v5/messages/fr.json b/v5/messages/fr.json new file mode 100644 index 0000000..669b760 --- /dev/null +++ b/v5/messages/fr.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "OUTILS", + "projects": "PROJETS", + "about": "À PROPOS", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "Navigation principale", + "utilityControlsLabel": "Contrôles utilitaires", + "themeToggleAria": "Changer le thème de couleur (système → clair → sombre)", + "themeToggleTitle": "Changer le thème de couleur", + "languageLabel": "Langue", + "languageSelectorAria": "Choisir la langue" + }, + "status": { + "labStatusLabel": "État du labo", + "toolsInInventory": "{count} OUTILS EN INVENTAIRE" + }, + "gallery": { + "title": "OUTILS // MACHINES", + "searchPlaceholder": "rechercher dans l'inventaire...", + "searchAria": "Rechercher dans l'inventaire", + "category": "CATÉGORIE :", + "training": "FORMATION :", + "viewModeLabel": "Mode d'affichage", + "grid": "[ GRILLE ]", + "table": "[ TABLEAU ]", + "toolGalleryLabel": "Galerie d'outils", + "empty": "Aucun outil correspondant trouvé.", + "loading": "chargement de l'inventaire", + "columnTool": "Outil", + "columnCategory": "Catégorie", + "columnZone": "Zone", + "columnTraining": "Formation", + "trainingBeginner": "Débutant", + "trainingIntermediate": "Intermédiaire", + "trainingAdvanced": "Avancé" + }, + "detail": { + "breadcrumbTools": "Outils", + "breadcrumbInventory": "Inventaire", + "toolStatusLabel": "État de l'outil", + "trainingChip": "formation {level}", + "ppeRequired": "EPI obligatoire", + "viewSafetyDoc": "Voir le document de sécurité", + "viewSop": "Voir la procédure (SOP)", + "atAGlanceLabel": "En un coup d'œil", + "materials": "Matériaux", + "contactStaff": "Contacter l'équipe du MakerLab", + "safetyAccess": "Sécurité et accès", + "ppeNotice": "Les EPI doivent être portés avant utilisation. Consultez les consignes affichées.", + "emergencyStop": "Arrêt d'urgence", + "emergencyStopFallback": "Suivez les consignes affichées du labo et prévenez l'équipe en cas d'urgence.", + "useRestrictions": "Restrictions d'utilisation", + "useRestrictionsFallback": "Ouvert à tous les utilisateurs formés du MakerLab pendant les heures d'ouverture.", + "documentsResources": "Documents et ressources", + "openResource": "Ouvrir la ressource", + "resourceFallback": "Ressource", + "noDocuments": "Aucun document lié pour l'instant.", + "details": "Détails", + "category": "Catégorie", + "location": "Emplacement", + "trainingRow": "Formation", + "mapId": "ID de carte", + "tags": "Étiquettes", + "notes": "Notes", + "physicalMachines": "Machines physiques", + "unit": "Unité", + "status": "État", + "condition": "Condition", + "serial": "Numéro de série", + "acquired": "Acquis le", + "notesTips": "Notes et conseils", + "backToTools": "‹ Retour à tous les outils" + }, + "projects": { + "eyebrow": "Projets", + "title": "Bientôt disponible", + "body": "Une galerie de projets du MakerLab est en préparation — réalisations d'étudiants, résultats de cours et vitrines sélectionnées par l'équipe. Pour l'instant, parcourez le catalogue d'outils pour voir ce qui est disponible au labo.", + "browseTools": "Parcourir les outils" + }, + "about": { + "eyebrow": "À propos", + "title": "Un catalogue dirigé par les étudiants pour le Cornell Tech MakerLab.", + "intro": "MakerLab Tools est un système numérique d'inventaire et de découverte pour le Cornell Tech MakerLab. Il aide les étudiants à parcourir l'équipement, à trouver la documentation de sécurité et à identifier quelles machines physiques sont disponibles au labo avant de s'y rendre.", + "origin": "Le projet a débuté comme un prototype de week-end et est devenu le catalogue actuel reposant sur Notion. Les outils, unités, emplacements et ressources résident tous dans Notion et alimentent le site via une couche d'API en lecture seule avec cache — de sorte que le personnel et les étudiants bénévoles peuvent gérer le contenu sans toucher au code.", + "builtByHeading": "Créé par", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. Conçu comme un projet dirigé par les étudiants afin de faciliter, pour la communauté du MakerLab, la recherche et l'utilisation sécurisée de l'équipement du labo.", + "howItWorksHeading": "Comment ça marche", + "howItWorksBody": "Le front-end est une application Next.js 16 déployée sur Vercel. L'API Notion sert de source de vérité pour le catalogue, avec une couche de cache par requête pour que la page reste rapide en cas de forte charge. Les images des outils sont mises en miroir localement afin de s'afficher de manière fiable même lorsque les URL signées en amont expirent.", + "feedbackBody": "Si vous repérez une erreur ou avez une suggestion, parlez-en à l'équipe du MakerLab — les corrections signalées sont examinées et mises à jour dans Notion.", + "browseTools": "Parcourir les outils" + }, + "chat": { + "openAria": "Ouvrir l'assistant MakerLab", + "title": "ASSISTANT MAKERLAB", + "newChatAria": "Démarrer une nouvelle conversation", + "newChatTitle": "Démarrer une nouvelle conversation", + "closeAria": "Fermer l'assistant", + "closeTitle": "Fermer (conserve la conversation)", + "closeScrimAria": "Fermer l'assistant MakerLab", + "greetingTool": "Posez vos questions sur cet outil — j'ai ses caractéristiques, ses matériaux et ses liens vers les ressources.", + "greetingGeneral": "Comment puis-je vous aider aujourd'hui ?", + "suggestionFindMachine": "Trouver une machine pour un projet", + "suggestionTraining": "Vérifier les exigences de formation", + "suggestionSafety": "Poser une question sur la sécurité ou les règles", + "composerPlaceholder": "Demandez à la console du labo...", + "composerAria": "Demandez à la console du labo", + "sendAria": "Envoyer", + "attachAria": "Joindre des photos", + "attachTitle": "Joindre des photos", + "removePhotoAria": "Retirer {name}", + "uploadingAria": "Téléversement de la photo", + "onlyImages": "Seuls les fichiers image sont pris en charge.", + "uploadFailed": "Échec du téléversement de la photo", + "toolRunningAria": "Outil en cours d'exécution", + "typingAria": "L'assistant est en train d'écrire", + "readingManualsAria": "Lecture des manuels", + "reading": "Lecture : {titles}…", + "error": "Une erreur s'est produite. Réessayez.", + "lookingUpUnit": "🔍 Recherche des détails de l'unité…", + "filingTicket": "📝 Création du ticket de maintenance…", + "working": "Traitement en cours…" + } +} diff --git a/v5/messages/he.json b/v5/messages/he.json new file mode 100644 index 0000000..93198cb --- /dev/null +++ b/v5/messages/he.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "כלים", + "projects": "פרויקטים", + "about": "אודות", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "ניווט ראשי", + "utilityControlsLabel": "פקדי עזר", + "themeToggleAria": "החלפת ערכת צבעים (מערכת ← בהיר ← כהה)", + "themeToggleTitle": "החלפת ערכת צבעים", + "languageLabel": "שפה", + "languageSelectorAria": "בחירת שפה" + }, + "status": { + "labStatusLabel": "מצב המעבדה", + "toolsInInventory": "{count} כלים במלאי" + }, + "gallery": { + "title": "כלים // מכונות", + "searchPlaceholder": "חיפוש במלאי...", + "searchAria": "חיפוש במלאי", + "category": "קטגוריה:", + "training": "הכשרה:", + "viewModeLabel": "מצב תצוגה", + "grid": "[ רשת ]", + "table": "[ טבלה ]", + "toolGalleryLabel": "גלריית כלים", + "empty": "לא נמצאו כלים תואמים.", + "loading": "טוען מלאי", + "columnTool": "כלי", + "columnCategory": "קטגוריה", + "columnZone": "אזור", + "columnTraining": "הכשרה", + "trainingBeginner": "מתחיל", + "trainingIntermediate": "בינוני", + "trainingAdvanced": "מתקדם" + }, + "detail": { + "breadcrumbTools": "כלים", + "breadcrumbInventory": "מלאי", + "toolStatusLabel": "מצב הכלי", + "trainingChip": "הכשרה {level}", + "ppeRequired": "נדרש ציוד מגן אישי", + "viewSafetyDoc": "הצגת מסמך הבטיחות", + "viewSop": "הצגת נוהל ההפעלה (SOP)", + "atAGlanceLabel": "במבט מהיר", + "materials": "חומרים", + "contactStaff": "פנו לצוות MakerLab", + "safetyAccess": "בטיחות וגישה", + "ppeNotice": "יש לחבוש ציוד מגן אישי לפני השימוש. עיינו בהנחיות המוצגות.", + "emergencyStop": "עצירת חירום", + "emergencyStopFallback": "פעלו לפי הנחיות המעבדה המוצגות והודיעו לצוות במקרה חירום.", + "useRestrictions": "הגבלות שימוש", + "useRestrictionsFallback": "פתוח לכל משתמשי MakerLab המוסמכים בשעות פעילות המעבדה.", + "documentsResources": "מסמכים ומשאבים", + "openResource": "פתיחת משאב", + "resourceFallback": "משאב", + "noDocuments": "עדיין אין מסמכים מקושרים.", + "details": "פרטים", + "category": "קטגוריה", + "location": "מיקום", + "trainingRow": "הכשרה", + "mapId": "מזהה מפה", + "tags": "תגיות", + "notes": "הערות", + "physicalMachines": "מכונות פיזיות", + "unit": "יחידה", + "status": "סטטוס", + "condition": "מצב", + "serial": "מספר סידורי", + "acquired": "נרכש בתאריך", + "notesTips": "הערות וטיפים", + "backToTools": "‹ חזרה לכל הכלים" + }, + "projects": { + "eyebrow": "פרויקטים", + "title": "בקרוב", + "body": "גלריית פרויקטים של MakerLab נמצאת בהכנה — יצירות סטודנטים, תוצרי קורסים ותצוגות שנבחרו על ידי הצוות. בינתיים, עיינו בקטלוג הכלים כדי לראות מה זמין במעבדה.", + "browseTools": "עיון בכלים" + }, + "about": { + "eyebrow": "אודות", + "title": "קטלוג בהובלת סטודנטים עבור Cornell Tech MakerLab.", + "intro": "MakerLab Tools היא מערכת דיגיטלית לניהול מלאי וגילוי עבור Cornell Tech MakerLab. היא עוזרת לסטודנטים לעיין בציוד, למצוא תיעוד בטיחות ולזהות אילו מכונות פיזיות זמינות במעבדה לפני שהם מגיעים אליה.", + "origin": "הפרויקט החל כאב טיפוס של סוף שבוע והתפתח לקטלוג הנוכחי המבוסס על Notion. הכלים, היחידות, המיקומים והמשאבים מאוחסנים כולם ב-Notion ומזינים את האתר דרך שכבת API לקריאה בלבד עם מטמון — כך שהצוות והסטודנטים המתנדבים יכולים לנהל את התוכן בלי לגעת בקוד.", + "builtByHeading": "נבנה על ידי", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. נבנה כפרויקט בהובלת סטודנטים כדי להקל על קהילת MakerLab למצוא ולהשתמש בבטחה בציוד המעבדה.", + "howItWorksHeading": "איך זה עובד", + "howItWorksBody": "צד הלקוח הוא אפליקציית Next.js 16 הפרוסה ב-Vercel. ה-API של Notion משמש כמקור האמת של הקטלוג, עם שכבת מטמון לכל בקשה כדי שהדף יישאר מהיר תחת עומס. תמונות הכלים משוכפלות מקומית כך שהן מוצגות באופן אמין גם כאשר כתובות ה-URL החתומות מהמקור פגות.", + "feedbackBody": "אם איתרתם טעות או יש לכם הצעה, דברו עם צוות MakerLab — תיקונים שסומנו נבדקים ומתעדכנים ב-Notion.", + "browseTools": "עיון בכלים" + }, + "chat": { + "openAria": "פתיחת עוזר MakerLab", + "title": "עוזר MAKERLAB", + "newChatAria": "התחלת צ'אט חדש", + "newChatTitle": "התחלת צ'אט חדש", + "closeAria": "סגירת העוזר", + "closeTitle": "סגירה (השיחה נשמרת)", + "closeScrimAria": "סגירת עוזר MakerLab", + "greetingTool": "שאלו על הכלי הזה — יש לי את המפרטים, החומרים וקישורי המשאבים שלו.", + "greetingGeneral": "כיצד אוכל לעזור לך היום?", + "suggestionFindMachine": "מציאת מכונה לפרויקט", + "suggestionTraining": "בדיקת דרישות הכשרה", + "suggestionSafety": "שאלה על בטיחות או נהלים", + "composerPlaceholder": "שאלו את קונסולת המעבדה...", + "composerAria": "שאלו את קונסולת המעבדה", + "sendAria": "שליחה", + "attachAria": "צירוף תמונות", + "attachTitle": "צירוף תמונות", + "removePhotoAria": "הסרת {name}", + "uploadingAria": "מעלה תמונה", + "onlyImages": "נתמכים קבצי תמונה בלבד.", + "uploadFailed": "העלאת התמונה נכשלה", + "toolRunningAria": "הכלי פועל", + "typingAria": "העוזר מקליד", + "readingManualsAria": "קורא מדריכים", + "reading": "קורא: {titles}…", + "error": "משהו השתבש. נסו שוב.", + "lookingUpUnit": "🔍 מחפש פרטי יחידה…", + "filingTicket": "📝 פותח כרטיס תחזוקה…", + "working": "עובד על זה…" + } +} diff --git a/v5/messages/hi.json b/v5/messages/hi.json new file mode 100644 index 0000000..8a947bd --- /dev/null +++ b/v5/messages/hi.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "उपकरण", + "projects": "परियोजनाएँ", + "about": "परिचय", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "मुख्य नेविगेशन", + "utilityControlsLabel": "उपयोगिता नियंत्रण", + "themeToggleAria": "रंग थीम बदलें (सिस्टम → हल्का → गहरा)", + "themeToggleTitle": "रंग थीम बदलें", + "languageLabel": "भाषा", + "languageSelectorAria": "भाषा चुनें" + }, + "status": { + "labStatusLabel": "लैब की स्थिति", + "toolsInInventory": "इन्वेंटरी में {count} उपकरण" + }, + "gallery": { + "title": "उपकरण // मशीनें", + "searchPlaceholder": "इन्वेंटरी खोजें...", + "searchAria": "इन्वेंटरी खोजें", + "category": "श्रेणी:", + "training": "प्रशिक्षण:", + "viewModeLabel": "व्यू मोड", + "grid": "[ ग्रिड ]", + "table": "[ तालिका ]", + "toolGalleryLabel": "उपकरण गैलरी", + "empty": "कोई मेल खाने वाला उपकरण नहीं मिला।", + "loading": "इन्वेंटरी लोड हो रही है", + "columnTool": "उपकरण", + "columnCategory": "श्रेणी", + "columnZone": "क्षेत्र", + "columnTraining": "प्रशिक्षण", + "trainingBeginner": "शुरुआती", + "trainingIntermediate": "मध्यम", + "trainingAdvanced": "उन्नत" + }, + "detail": { + "breadcrumbTools": "उपकरण", + "breadcrumbInventory": "इन्वेंटरी", + "toolStatusLabel": "उपकरण की स्थिति", + "trainingChip": "{level} प्रशिक्षण", + "ppeRequired": "PPE आवश्यक", + "viewSafetyDoc": "सुरक्षा दस्तावेज़ देखें", + "viewSop": "SOP देखें", + "atAGlanceLabel": "एक नज़र में", + "materials": "सामग्री", + "contactStaff": "MakerLab स्टाफ़ से संपर्क करें", + "safetyAccess": "सुरक्षा और पहुँच", + "ppeNotice": "उपयोग से पहले PPE पहनना अनिवार्य है। लगाए गए दिशानिर्देश देखें।", + "emergencyStop": "आपातकालीन स्टॉप", + "emergencyStopFallback": "लगाए गए लैब दिशानिर्देशों का पालन करें और आपात स्थिति में स्टाफ़ को सूचित करें।", + "useRestrictions": "उपयोग प्रतिबंध", + "useRestrictionsFallback": "लैब समय के दौरान सभी प्रशिक्षित MakerLab उपयोगकर्ताओं के लिए खुला।", + "documentsResources": "दस्तावेज़ और संसाधन", + "openResource": "संसाधन खोलें", + "resourceFallback": "संसाधन", + "noDocuments": "अभी तक कोई दस्तावेज़ लिंक नहीं किया गया।", + "details": "विवरण", + "category": "श्रेणी", + "location": "स्थान", + "trainingRow": "प्रशिक्षण", + "mapId": "मानचित्र आईडी", + "tags": "टैग", + "notes": "नोट्स", + "physicalMachines": "भौतिक मशीनें", + "unit": "इकाई", + "status": "स्थिति", + "condition": "हालत", + "serial": "सीरियल", + "acquired": "अधिग्रहण तिथि", + "notesTips": "नोट्स और सुझाव", + "backToTools": "‹ सभी उपकरणों पर वापस जाएँ" + }, + "projects": { + "eyebrow": "परियोजनाएँ", + "title": "जल्द आ रहा है", + "body": "MakerLab परियोजनाओं की एक गैलरी तैयार की जा रही है — छात्रों की रचनाएँ, पाठ्यक्रम परिणाम और स्टाफ़ द्वारा चुनी गई प्रस्तुतियाँ। फ़िलहाल, लैब में उपलब्ध चीज़ें देखने के लिए उपकरण सूची ब्राउज़ करें।", + "browseTools": "उपकरण ब्राउज़ करें" + }, + "about": { + "eyebrow": "परिचय", + "title": "Cornell Tech MakerLab के लिए छात्र-नेतृत्व वाली सूची।", + "intro": "MakerLab Tools, Cornell Tech MakerLab के लिए एक डिजिटल इन्वेंटरी और खोज प्रणाली है। यह छात्रों को उपकरण ब्राउज़ करने, सुरक्षा दस्तावेज़ ढूँढने और लैब जाने से पहले यह जानने में मदद करती है कि कौन-सी भौतिक मशीनें उपलब्ध हैं।", + "origin": "यह परियोजना एक सप्ताहांत प्रोटोटाइप के रूप में शुरू हुई और मौजूदा Notion-आधारित सूची में विकसित हुई। उपकरण, इकाइयाँ, स्थान और संसाधन सभी Notion में रहते हैं और एक कैश्ड रीड-ओनली API परत के ज़रिए साइट को डेटा देते हैं — ताकि स्टाफ़ और छात्र स्वयंसेवक कोड को छुए बिना सामग्री प्रबंधित कर सकें।", + "builtByHeading": "निर्माता", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. इसे एक छात्र-नेतृत्व वाली परियोजना के रूप में बनाया गया ताकि MakerLab समुदाय के लिए लैब के उपकरण ढूँढना और सुरक्षित रूप से उपयोग करना आसान हो।", + "howItWorksHeading": "यह कैसे काम करता है", + "howItWorksBody": "फ्रंट एंड एक Next.js 16 ऐप है जो Vercel पर तैनात है। Notion API सूची के लिए सत्य का स्रोत है, जिसमें प्रति-अनुरोध कैश परत होती है ताकि पेज भार के तहत तेज़ बना रहे। उपकरण की छवियाँ स्थानीय रूप से मिरर की जाती हैं ताकि अपस्ट्रीम साइन किए गए URL समाप्त होने पर भी वे विश्वसनीय रूप से प्रदर्शित हों।", + "feedbackBody": "यदि आपको कोई त्रुटि दिखे या कोई सुझाव हो, तो MakerLab स्टाफ़ से बात करें — चिह्नित सुधारों की समीक्षा की जाती है और Notion में अपडेट किया जाता है।", + "browseTools": "उपकरण ब्राउज़ करें" + }, + "chat": { + "openAria": "MakerLab सहायक खोलें", + "title": "MAKERLAB सहायक", + "newChatAria": "नई चैट शुरू करें", + "newChatTitle": "नई चैट शुरू करें", + "closeAria": "सहायक बंद करें", + "closeTitle": "बंद करें (बातचीत सुरक्षित रहेगी)", + "closeScrimAria": "MakerLab सहायक बंद करें", + "greetingTool": "इस उपकरण के बारे में पूछें — मेरे पास इसके स्पेक्स, सामग्री और संसाधन लिंक हैं।", + "greetingGeneral": "आज मैं आपकी कैसे मदद कर सकता हूँ?", + "suggestionFindMachine": "किसी परियोजना के लिए मशीन ढूँढें", + "suggestionTraining": "प्रशिक्षण आवश्यकताएँ जाँचें", + "suggestionSafety": "सुरक्षा या नीति के बारे में पूछें", + "composerPlaceholder": "लैब कंसोल से पूछें...", + "composerAria": "लैब कंसोल से पूछें", + "sendAria": "भेजें", + "attachAria": "फ़ोटो संलग्न करें", + "attachTitle": "फ़ोटो संलग्न करें", + "removePhotoAria": "{name} हटाएँ", + "uploadingAria": "फ़ोटो अपलोड हो रही है", + "onlyImages": "केवल छवि फ़ाइलें समर्थित हैं।", + "uploadFailed": "फ़ोटो अपलोड विफल", + "toolRunningAria": "उपकरण चल रहा है", + "typingAria": "सहायक टाइप कर रहा है", + "readingManualsAria": "मैनुअल पढ़ रहा है", + "reading": "पढ़ रहा है: {titles}…", + "error": "कुछ गड़बड़ हो गई। फिर से प्रयास करें।", + "lookingUpUnit": "🔍 इकाई का विवरण देख रहा है…", + "filingTicket": "📝 रखरखाव टिकट दर्ज कर रहा है…", + "working": "काम चल रहा है…" + } +} diff --git a/v5/messages/ja.json b/v5/messages/ja.json new file mode 100644 index 0000000..3c0c0a7 --- /dev/null +++ b/v5/messages/ja.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "ツール", + "projects": "プロジェクト", + "about": "概要", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "メインナビゲーション", + "utilityControlsLabel": "ユーティリティコントロール", + "themeToggleAria": "カラーテーマを切り替え(システム → ライト → ダーク)", + "themeToggleTitle": "カラーテーマを切り替え", + "languageLabel": "言語", + "languageSelectorAria": "言語を選択" + }, + "status": { + "labStatusLabel": "ラボの状況", + "toolsInInventory": "在庫ツール {count} 件" + }, + "gallery": { + "title": "ツール // 機材", + "searchPlaceholder": "在庫を検索...", + "searchAria": "在庫を検索", + "category": "カテゴリ:", + "training": "トレーニング:", + "viewModeLabel": "表示モード", + "grid": "[ グリッド ]", + "table": "[ テーブル ]", + "toolGalleryLabel": "ツールギャラリー", + "empty": "一致するツールが見つかりません。", + "loading": "在庫を読み込み中", + "columnTool": "ツール", + "columnCategory": "カテゴリ", + "columnZone": "ゾーン", + "columnTraining": "トレーニング", + "trainingBeginner": "初級", + "trainingIntermediate": "中級", + "trainingAdvanced": "上級" + }, + "detail": { + "breadcrumbTools": "ツール", + "breadcrumbInventory": "在庫", + "toolStatusLabel": "ツールの状態", + "trainingChip": "{level}トレーニング", + "ppeRequired": "PPE 必須", + "viewSafetyDoc": "安全文書を表示", + "viewSop": "SOP を表示", + "atAGlanceLabel": "概要", + "materials": "素材", + "contactStaff": "MakerLab スタッフにお問い合わせください", + "safetyAccess": "安全と利用条件", + "ppeNotice": "使用前に PPE を着用してください。掲示されたガイドラインを確認してください。", + "emergencyStop": "緊急停止", + "emergencyStopFallback": "掲示されたラボのガイドラインに従い、緊急時にはスタッフに連絡してください。", + "useRestrictions": "利用制限", + "useRestrictionsFallback": "ラボ開放時間中、トレーニングを受けたすべての MakerLab 利用者が使用できます。", + "documentsResources": "ドキュメントとリソース", + "openResource": "リソースを開く", + "resourceFallback": "リソース", + "noDocuments": "リンクされたドキュメントはまだありません。", + "details": "詳細", + "category": "カテゴリ", + "location": "場所", + "trainingRow": "トレーニング", + "mapId": "マップ ID", + "tags": "タグ", + "notes": "メモ", + "physicalMachines": "実機", + "unit": "ユニット", + "status": "状態", + "condition": "コンディション", + "serial": "シリアル番号", + "acquired": "取得日", + "notesTips": "メモとヒント", + "backToTools": "‹ すべてのツールに戻る" + }, + "projects": { + "eyebrow": "プロジェクト", + "title": "近日公開", + "body": "MakerLab プロジェクトのギャラリーを準備中です。学生の制作物、授業の成果、スタッフが厳選した展示などを掲載予定です。今のところは、ツールカタログを閲覧してラボで利用できるものをご確認ください。", + "browseTools": "ツールを閲覧" + }, + "about": { + "eyebrow": "概要", + "title": "Cornell Tech MakerLab のための学生主導のカタログ。", + "intro": "MakerLab Tools は、Cornell Tech MakerLab 向けのデジタル在庫・発見システムです。学生が機材を閲覧し、安全文書を見つけ、ラボに行く前にどの実機が利用可能かを把握できるよう支援します。", + "origin": "このプロジェクトは週末の試作として始まり、現在の Notion ベースのカタログへと発展しました。ツール、ユニット、場所、リソースはすべて Notion に保存され、キャッシュ付きの読み取り専用 API レイヤーを通じてサイトに提供されます。これにより、スタッフや学生ボランティアはコードに触れることなくコンテンツを管理できます。", + "builtByHeading": "制作者", + "builtByBody": "Isaac Steinberg、Johnson Cornell Tech MBA '26。MakerLab コミュニティがラボの機材をより簡単に見つけ、安全に使えるようにするための学生主導プロジェクトとして制作されました。", + "howItWorksHeading": "仕組み", + "howItWorksBody": "フロントエンドは Vercel にデプロイされた Next.js 16 アプリです。Notion API がカタログの信頼できる情報源として機能し、リクエストごとのキャッシュレイヤーにより高負荷時でもページが高速に保たれます。ツール画像はローカルにミラーリングされているため、上流の署名付き URL が期限切れになっても確実に表示されます。", + "feedbackBody": "誤りに気づいた場合やご提案がある場合は、MakerLab スタッフにお声がけください。報告された修正は確認のうえ Notion に反映されます。", + "browseTools": "ツールを閲覧" + }, + "chat": { + "openAria": "MakerLab アシスタントを開く", + "title": "MAKERLAB アシスタント", + "newChatAria": "新しいチャットを開始", + "newChatTitle": "新しいチャットを開始", + "closeAria": "アシスタントを閉じる", + "closeTitle": "閉じる(会話は保持されます)", + "closeScrimAria": "MakerLab アシスタントを閉じる", + "greetingTool": "このツールについて質問してください。仕様、素材、リソースリンクを把握しています。", + "greetingGeneral": "本日はどのようなご用件でしょうか?", + "suggestionFindMachine": "プロジェクトに合う機材を探す", + "suggestionTraining": "トレーニング要件を確認する", + "suggestionSafety": "安全やポリシーについて尋ねる", + "composerPlaceholder": "ラボコンソールに質問...", + "composerAria": "ラボコンソールに質問", + "sendAria": "送信", + "attachAria": "写真を添付", + "attachTitle": "写真を添付", + "removePhotoAria": "{name} を削除", + "uploadingAria": "写真をアップロード中", + "onlyImages": "画像ファイルのみ対応しています。", + "uploadFailed": "写真のアップロードに失敗しました", + "toolRunningAria": "ツール実行中", + "typingAria": "アシスタントが入力中です", + "readingManualsAria": "マニュアルを読み込み中", + "reading": "読み込み中:{titles}…", + "error": "問題が発生しました。もう一度お試しください。", + "lookingUpUnit": "🔍 ユニットの詳細を確認中…", + "filingTicket": "📝 メンテナンスチケットを作成中…", + "working": "処理中…" + } +} diff --git a/v5/messages/ko.json b/v5/messages/ko.json new file mode 100644 index 0000000..630567a --- /dev/null +++ b/v5/messages/ko.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "도구", + "projects": "프로젝트", + "about": "소개", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "주요 내비게이션", + "utilityControlsLabel": "유틸리티 컨트롤", + "themeToggleAria": "색상 테마 전환 (시스템 → 라이트 → 다크)", + "themeToggleTitle": "색상 테마 전환", + "languageLabel": "언어", + "languageSelectorAria": "언어 선택" + }, + "status": { + "labStatusLabel": "랩 상태", + "toolsInInventory": "재고 도구 {count}개" + }, + "gallery": { + "title": "도구 // 장비", + "searchPlaceholder": "재고 검색...", + "searchAria": "재고 검색", + "category": "카테고리:", + "training": "교육:", + "viewModeLabel": "보기 모드", + "grid": "[ 그리드 ]", + "table": "[ 테이블 ]", + "toolGalleryLabel": "도구 갤러리", + "empty": "일치하는 도구를 찾을 수 없습니다.", + "loading": "재고 로드 중", + "columnTool": "도구", + "columnCategory": "카테고리", + "columnZone": "구역", + "columnTraining": "교육", + "trainingBeginner": "초급", + "trainingIntermediate": "중급", + "trainingAdvanced": "고급" + }, + "detail": { + "breadcrumbTools": "도구", + "breadcrumbInventory": "재고", + "toolStatusLabel": "도구 상태", + "trainingChip": "{level} 교육", + "ppeRequired": "보호장비 필수", + "viewSafetyDoc": "안전 문서 보기", + "viewSop": "SOP 보기", + "atAGlanceLabel": "한눈에 보기", + "materials": "재료", + "contactStaff": "MakerLab 직원에게 문의하세요", + "safetyAccess": "안전 및 사용 권한", + "ppeNotice": "사용 전에 보호장비를 착용해야 합니다. 게시된 지침을 확인하세요.", + "emergencyStop": "비상 정지", + "emergencyStopFallback": "게시된 랩 지침을 따르고 비상 시 직원에게 알리세요.", + "useRestrictions": "사용 제한", + "useRestrictionsFallback": "랩 운영 시간 동안 교육을 받은 모든 MakerLab 사용자에게 개방됩니다.", + "documentsResources": "문서 및 자료", + "openResource": "자료 열기", + "resourceFallback": "자료", + "noDocuments": "아직 연결된 문서가 없습니다.", + "details": "세부 정보", + "category": "카테고리", + "location": "위치", + "trainingRow": "교육", + "mapId": "지도 ID", + "tags": "태그", + "notes": "메모", + "physicalMachines": "실물 장비", + "unit": "장치", + "status": "상태", + "condition": "상태(컨디션)", + "serial": "일련번호", + "acquired": "취득일", + "notesTips": "메모 및 팁", + "backToTools": "‹ 모든 도구로 돌아가기" + }, + "projects": { + "eyebrow": "프로젝트", + "title": "준비 중", + "body": "MakerLab 프로젝트 갤러리를 준비 중입니다 — 학생 작품, 강의 결과물, 직원이 선별한 전시물 등. 지금은 도구 카탈로그를 둘러보며 랩에서 사용할 수 있는 항목을 확인하세요.", + "browseTools": "도구 둘러보기" + }, + "about": { + "eyebrow": "소개", + "title": "Cornell Tech MakerLab을 위한 학생 주도 카탈로그.", + "intro": "MakerLab Tools는 Cornell Tech MakerLab을 위한 디지털 재고 및 탐색 시스템입니다. 학생들이 장비를 둘러보고, 안전 문서를 찾고, 랩에 가기 전에 어떤 실물 장비를 사용할 수 있는지 확인할 수 있도록 돕습니다.", + "origin": "이 프로젝트는 주말 프로토타입으로 시작해 현재의 Notion 기반 카탈로그로 발전했습니다. 도구, 장치, 위치, 자료가 모두 Notion에 저장되어 있으며 캐시된 읽기 전용 API 레이어를 통해 사이트에 제공됩니다. 덕분에 직원과 학생 자원봉사자가 코드를 건드리지 않고도 콘텐츠를 관리할 수 있습니다.", + "builtByHeading": "제작자", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. MakerLab 커뮤니티가 랩 장비를 더 쉽게 찾고 안전하게 사용할 수 있도록 학생 주도 프로젝트로 제작했습니다.", + "howItWorksHeading": "작동 방식", + "howItWorksBody": "프런트엔드는 Vercel에 배포된 Next.js 16 앱입니다. Notion API가 카탈로그의 신뢰 가능한 출처 역할을 하며, 요청별 캐시 레이어를 통해 부하가 높아도 페이지가 빠르게 유지됩니다. 도구 이미지는 로컬에 미러링되어 업스트림 서명 URL이 만료되어도 안정적으로 표시됩니다.", + "feedbackBody": "오류를 발견하거나 제안이 있으면 MakerLab 직원에게 알려주세요. 표시된 수정 사항은 검토 후 Notion에서 업데이트됩니다.", + "browseTools": "도구 둘러보기" + }, + "chat": { + "openAria": "MakerLab 어시스턴트 열기", + "title": "MAKERLAB 어시스턴트", + "newChatAria": "새 대화 시작", + "newChatTitle": "새 대화 시작", + "closeAria": "어시스턴트 닫기", + "closeTitle": "닫기 (대화 유지)", + "closeScrimAria": "MakerLab 어시스턴트 닫기", + "greetingTool": "이 도구에 대해 물어보세요. 사양, 재료, 자료 링크를 알고 있습니다.", + "greetingGeneral": "오늘 무엇을 도와드릴까요?", + "suggestionFindMachine": "프로젝트에 맞는 장비 찾기", + "suggestionTraining": "교육 요건 확인", + "suggestionSafety": "안전 또는 정책 문의", + "composerPlaceholder": "랩 콘솔에 물어보세요...", + "composerAria": "랩 콘솔에 물어보기", + "sendAria": "보내기", + "attachAria": "사진 첨부", + "attachTitle": "사진 첨부", + "removePhotoAria": "{name} 제거", + "uploadingAria": "사진 업로드 중", + "onlyImages": "이미지 파일만 지원됩니다.", + "uploadFailed": "사진 업로드 실패", + "toolRunningAria": "도구 실행 중", + "typingAria": "어시스턴트가 입력 중입니다", + "readingManualsAria": "매뉴얼 읽는 중", + "reading": "읽는 중: {titles}…", + "error": "문제가 발생했습니다. 다시 시도하세요.", + "lookingUpUnit": "🔍 장치 세부 정보 조회 중…", + "filingTicket": "📝 유지보수 티켓 등록 중…", + "working": "처리 중…" + } +} diff --git a/v5/messages/pt-BR.json b/v5/messages/pt-BR.json new file mode 100644 index 0000000..8ad29ac --- /dev/null +++ b/v5/messages/pt-BR.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "FERRAMENTAS", + "projects": "PROJETOS", + "about": "SOBRE", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "Navegação principal", + "utilityControlsLabel": "Controles utilitários", + "themeToggleAria": "Alternar tema de cores (sistema → claro → escuro)", + "themeToggleTitle": "Alternar tema de cores", + "languageLabel": "Idioma", + "languageSelectorAria": "Selecionar idioma" + }, + "status": { + "labStatusLabel": "Status do laboratório", + "toolsInInventory": "{count} FERRAMENTAS NO INVENTÁRIO" + }, + "gallery": { + "title": "FERRAMENTAS // MÁQUINAS", + "searchPlaceholder": "buscar no inventário...", + "searchAria": "Buscar no inventário", + "category": "CATEGORIA:", + "training": "TREINAMENTO:", + "viewModeLabel": "Modo de visualização", + "grid": "[ GRADE ]", + "table": "[ TABELA ]", + "toolGalleryLabel": "Galeria de ferramentas", + "empty": "Nenhuma ferramenta correspondente encontrada.", + "loading": "carregando inventário", + "columnTool": "Ferramenta", + "columnCategory": "Categoria", + "columnZone": "Zona", + "columnTraining": "Treinamento", + "trainingBeginner": "Iniciante", + "trainingIntermediate": "Intermediário", + "trainingAdvanced": "Avançado" + }, + "detail": { + "breadcrumbTools": "Ferramentas", + "breadcrumbInventory": "Inventário", + "toolStatusLabel": "Status da ferramenta", + "trainingChip": "treinamento {level}", + "ppeRequired": "EPI obrigatório", + "viewSafetyDoc": "Ver documento de segurança", + "viewSop": "Ver procedimento (SOP)", + "atAGlanceLabel": "Visão geral", + "materials": "Materiais", + "contactStaff": "Entre em contato com a equipe do MakerLab", + "safetyAccess": "Segurança e acesso", + "ppeNotice": "O EPI deve ser usado antes da utilização. Consulte as orientações afixadas.", + "emergencyStop": "Parada de emergência", + "emergencyStopFallback": "Siga as orientações afixadas do laboratório e avise a equipe em caso de emergência.", + "useRestrictions": "Restrições de uso", + "useRestrictionsFallback": "Aberto a todos os usuários treinados do MakerLab durante o horário do laboratório.", + "documentsResources": "Documentos e recursos", + "openResource": "Abrir recurso", + "resourceFallback": "Recurso", + "noDocuments": "Nenhum documento vinculado ainda.", + "details": "Detalhes", + "category": "Categoria", + "location": "Local", + "trainingRow": "Treinamento", + "mapId": "ID do mapa", + "tags": "Etiquetas", + "notes": "Notas", + "physicalMachines": "Máquinas físicas", + "unit": "Unidade", + "status": "Status", + "condition": "Condição", + "serial": "Número de série", + "acquired": "Adquirido em", + "notesTips": "Notas e dicas", + "backToTools": "‹ Voltar a todas as ferramentas" + }, + "projects": { + "eyebrow": "Projetos", + "title": "Em breve", + "body": "Uma galeria de projetos do MakerLab está em desenvolvimento — criações de estudantes, resultados de cursos e mostras selecionadas pela equipe. Por enquanto, navegue pelo catálogo de ferramentas para ver o que está disponível no laboratório.", + "browseTools": "Explorar ferramentas" + }, + "about": { + "eyebrow": "Sobre", + "title": "Um catálogo liderado por estudantes para o Cornell Tech MakerLab.", + "intro": "O MakerLab Tools é um sistema digital de inventário e descoberta para o Cornell Tech MakerLab. Ele ajuda os estudantes a navegar pelo equipamento, encontrar documentação de segurança e identificar quais máquinas físicas estão disponíveis no laboratório antes de irem até lá.", + "origin": "O projeto começou como um protótipo de fim de semana e cresceu até se tornar o catálogo atual baseado no Notion. Ferramentas, unidades, locais e recursos ficam todos no Notion e alimentam o site por meio de uma camada de API somente leitura com cache — assim, a equipe e os estudantes voluntários podem gerenciar o conteúdo sem mexer no código.", + "builtByHeading": "Criado por", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. Criado como um projeto liderado por estudantes para facilitar que a comunidade do MakerLab encontre e use com segurança o equipamento do laboratório.", + "howItWorksHeading": "Como funciona", + "howItWorksBody": "O front-end é um aplicativo Next.js 16 implantado na Vercel. A API do Notion serve como fonte da verdade para o catálogo, com uma camada de cache por requisição para que a página continue rápida sob carga. As imagens das ferramentas são espelhadas localmente para que sejam exibidas de forma confiável mesmo quando as URLs assinadas de origem expiram.", + "feedbackBody": "Se você notar algum erro ou tiver uma sugestão, fale com a equipe do MakerLab — as correções sinalizadas são revisadas e atualizadas no Notion.", + "browseTools": "Explorar ferramentas" + }, + "chat": { + "openAria": "Abrir o assistente do MakerLab", + "title": "ASSISTENTE MAKERLAB", + "newChatAria": "Iniciar nova conversa", + "newChatTitle": "Iniciar nova conversa", + "closeAria": "Fechar assistente", + "closeTitle": "Fechar (mantém a conversa)", + "closeScrimAria": "Fechar o assistente do MakerLab", + "greetingTool": "Pergunte sobre esta ferramenta — tenho suas especificações, materiais e links de recursos.", + "greetingGeneral": "Como posso ajudar você hoje?", + "suggestionFindMachine": "Encontrar uma máquina para um projeto", + "suggestionTraining": "Verificar requisitos de treinamento", + "suggestionSafety": "Perguntar sobre segurança ou normas", + "composerPlaceholder": "Pergunte ao console do laboratório...", + "composerAria": "Pergunte ao console do laboratório", + "sendAria": "Enviar", + "attachAria": "Anexar fotos", + "attachTitle": "Anexar fotos", + "removePhotoAria": "Remover {name}", + "uploadingAria": "Enviando foto", + "onlyImages": "Somente arquivos de imagem são aceitos.", + "uploadFailed": "Falha ao enviar a foto", + "toolRunningAria": "Ferramenta em execução", + "typingAria": "O assistente está digitando", + "readingManualsAria": "Lendo manuais", + "reading": "Lendo: {titles}…", + "error": "Algo deu errado. Tente novamente.", + "lookingUpUnit": "🔍 Consultando detalhes da unidade…", + "filingTicket": "📝 Registrando chamado de manutenção…", + "working": "Trabalhando nisso…" + } +} diff --git a/v5/messages/ru.json b/v5/messages/ru.json new file mode 100644 index 0000000..73fc270 --- /dev/null +++ b/v5/messages/ru.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "ИНСТРУМЕНТЫ", + "projects": "ПРОЕКТЫ", + "about": "О ПРОЕКТЕ", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "Основная навигация", + "utilityControlsLabel": "Служебные элементы управления", + "themeToggleAria": "Переключить цветовую тему (система → светлая → тёмная)", + "themeToggleTitle": "Переключить цветовую тему", + "languageLabel": "Язык", + "languageSelectorAria": "Выбрать язык" + }, + "status": { + "labStatusLabel": "Статус лаборатории", + "toolsInInventory": "ИНСТРУМЕНТОВ НА СКЛАДЕ: {count}" + }, + "gallery": { + "title": "ИНСТРУМЕНТЫ // СТАНКИ", + "searchPlaceholder": "поиск по инвентарю...", + "searchAria": "Поиск по инвентарю", + "category": "КАТЕГОРИЯ:", + "training": "ОБУЧЕНИЕ:", + "viewModeLabel": "Режим просмотра", + "grid": "[ СЕТКА ]", + "table": "[ ТАБЛИЦА ]", + "toolGalleryLabel": "Галерея инструментов", + "empty": "Подходящие инструменты не найдены.", + "loading": "загрузка инвентаря", + "columnTool": "Инструмент", + "columnCategory": "Категория", + "columnZone": "Зона", + "columnTraining": "Обучение", + "trainingBeginner": "Начальный", + "trainingIntermediate": "Средний", + "trainingAdvanced": "Продвинутый" + }, + "detail": { + "breadcrumbTools": "Инструменты", + "breadcrumbInventory": "Инвентарь", + "toolStatusLabel": "Статус инструмента", + "trainingChip": "обучение: {level}", + "ppeRequired": "Требуются СИЗ", + "viewSafetyDoc": "Открыть документ по безопасности", + "viewSop": "Открыть регламент (SOP)", + "atAGlanceLabel": "Краткий обзор", + "materials": "Материалы", + "contactStaff": "Обратитесь к сотрудникам MakerLab", + "safetyAccess": "Безопасность и доступ", + "ppeNotice": "Перед использованием необходимо надеть СИЗ. Ознакомьтесь с размещёнными инструкциями.", + "emergencyStop": "Аварийная остановка", + "emergencyStopFallback": "Следуйте размещённым инструкциям лаборатории и сообщайте сотрудникам в случае чрезвычайной ситуации.", + "useRestrictions": "Ограничения использования", + "useRestrictionsFallback": "Доступно всем обученным пользователям MakerLab в часы работы лаборатории.", + "documentsResources": "Документы и ресурсы", + "openResource": "Открыть ресурс", + "resourceFallback": "Ресурс", + "noDocuments": "Документы пока не привязаны.", + "details": "Подробности", + "category": "Категория", + "location": "Расположение", + "trainingRow": "Обучение", + "mapId": "ID на карте", + "tags": "Теги", + "notes": "Заметки", + "physicalMachines": "Физические станки", + "unit": "Единица", + "status": "Статус", + "condition": "Состояние", + "serial": "Серийный номер", + "acquired": "Приобретено", + "notesTips": "Заметки и советы", + "backToTools": "‹ Назад ко всем инструментам" + }, + "projects": { + "eyebrow": "Проекты", + "title": "Скоро", + "body": "Готовится галерея проектов MakerLab — студенческие работы, результаты курсов и подборки от сотрудников. А пока просмотрите каталог инструментов, чтобы узнать, что доступно в лаборатории.", + "browseTools": "Просмотреть инструменты" + }, + "about": { + "eyebrow": "О проекте", + "title": "Каталог под руководством студентов для Cornell Tech MakerLab.", + "intro": "MakerLab Tools — это цифровая система учёта и поиска оборудования для Cornell Tech MakerLab. Она помогает студентам просматривать оборудование, находить документацию по безопасности и узнавать, какие физические станки доступны в лаборатории, прежде чем туда идти.", + "origin": "Проект начался как прототип выходного дня и вырос в нынешний каталог на базе Notion. Инструменты, единицы оборудования, расположения и ресурсы хранятся в Notion и передаются на сайт через кэшируемый слой API только для чтения — поэтому сотрудники и студенты-волонтёры могут управлять контентом, не трогая код.", + "builtByHeading": "Автор", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. Создано как студенческий проект, чтобы сообществу MakerLab было проще находить и безопасно использовать оборудование лаборатории.", + "howItWorksHeading": "Как это работает", + "howItWorksBody": "Фронтенд — это приложение Next.js 16, развёрнутое на Vercel. API Notion служит источником истины для каталога, а слой кэширования на каждый запрос обеспечивает быструю работу страницы под нагрузкой. Изображения инструментов зеркалируются локально, поэтому они надёжно отображаются даже после истечения срока действия подписанных исходных URL.", + "feedbackBody": "Если вы заметили ошибку или у вас есть предложение, сообщите сотрудникам MakerLab — отмеченные исправления проверяются и обновляются в Notion.", + "browseTools": "Просмотреть инструменты" + }, + "chat": { + "openAria": "Открыть ассистента MakerLab", + "title": "АССИСТЕНТ MAKERLAB", + "newChatAria": "Начать новый чат", + "newChatTitle": "Начать новый чат", + "closeAria": "Закрыть ассистента", + "closeTitle": "Закрыть (диалог сохранится)", + "closeScrimAria": "Закрыть ассистента MakerLab", + "greetingTool": "Спросите об этом инструменте — у меня есть его характеристики, материалы и ссылки на ресурсы.", + "greetingGeneral": "Чем я могу помочь вам сегодня?", + "suggestionFindMachine": "Подобрать станок для проекта", + "suggestionTraining": "Проверить требования к обучению", + "suggestionSafety": "Спросить о безопасности или правилах", + "composerPlaceholder": "Спросите у консоли лаборатории...", + "composerAria": "Спросите у консоли лаборатории", + "sendAria": "Отправить", + "attachAria": "Прикрепить фото", + "attachTitle": "Прикрепить фото", + "removePhotoAria": "Удалить {name}", + "uploadingAria": "Загрузка фото", + "onlyImages": "Поддерживаются только файлы изображений.", + "uploadFailed": "Не удалось загрузить фото", + "toolRunningAria": "Инструмент выполняется", + "typingAria": "Ассистент печатает", + "readingManualsAria": "Чтение руководств", + "reading": "Чтение: {titles}…", + "error": "Что-то пошло не так. Попробуйте снова.", + "lookingUpUnit": "🔍 Поиск сведений об оборудовании…", + "filingTicket": "📝 Создание заявки на обслуживание…", + "working": "Работаю над этим…" + } +} diff --git a/v5/messages/tr.json b/v5/messages/tr.json new file mode 100644 index 0000000..1749086 --- /dev/null +++ b/v5/messages/tr.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "ARAÇLAR", + "projects": "PROJELER", + "about": "HAKKINDA", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "Ana gezinme", + "utilityControlsLabel": "Yardımcı denetimler", + "themeToggleAria": "Renk temasını değiştir (sistem → açık → koyu)", + "themeToggleTitle": "Renk temasını değiştir", + "languageLabel": "Dil", + "languageSelectorAria": "Dil seç" + }, + "status": { + "labStatusLabel": "Laboratuvar durumu", + "toolsInInventory": "ENVANTERDE {count} ARAÇ" + }, + "gallery": { + "title": "ARAÇLAR // MAKİNELER", + "searchPlaceholder": "envanterde ara...", + "searchAria": "Envanterde ara", + "category": "KATEGORİ:", + "training": "EĞİTİM:", + "viewModeLabel": "Görünüm modu", + "grid": "[ IZGARA ]", + "table": "[ TABLO ]", + "toolGalleryLabel": "Araç galerisi", + "empty": "Eşleşen araç bulunamadı.", + "loading": "envanter yükleniyor", + "columnTool": "Araç", + "columnCategory": "Kategori", + "columnZone": "Bölge", + "columnTraining": "Eğitim", + "trainingBeginner": "Başlangıç", + "trainingIntermediate": "Orta", + "trainingAdvanced": "İleri" + }, + "detail": { + "breadcrumbTools": "Araçlar", + "breadcrumbInventory": "Envanter", + "toolStatusLabel": "Araç durumu", + "trainingChip": "{level} eğitim", + "ppeRequired": "KKD gerekli", + "viewSafetyDoc": "Güvenlik belgesini görüntüle", + "viewSop": "SOP'yi görüntüle", + "atAGlanceLabel": "Bir bakışta", + "materials": "Malzemeler", + "contactStaff": "MakerLab ekibiyle iletişime geçin", + "safetyAccess": "Güvenlik ve erişim", + "ppeNotice": "Kullanımdan önce KKD takılmalıdır. Asılı yönergeleri inceleyin.", + "emergencyStop": "Acil durdurma", + "emergencyStopFallback": "Asılı laboratuvar yönergelerini izleyin ve acil durumda ekibe haber verin.", + "useRestrictions": "Kullanım kısıtlamaları", + "useRestrictionsFallback": "Laboratuvar saatleri içinde eğitimli tüm MakerLab kullanıcılarına açıktır.", + "documentsResources": "Belgeler ve kaynaklar", + "openResource": "Kaynağı aç", + "resourceFallback": "Kaynak", + "noDocuments": "Henüz bağlı belge yok.", + "details": "Ayrıntılar", + "category": "Kategori", + "location": "Konum", + "trainingRow": "Eğitim", + "mapId": "Harita kimliği", + "tags": "Etiketler", + "notes": "Notlar", + "physicalMachines": "Fiziksel makineler", + "unit": "Birim", + "status": "Durum", + "condition": "Durum (koşul)", + "serial": "Seri numarası", + "acquired": "Edinilme tarihi", + "notesTips": "Notlar ve ipuçları", + "backToTools": "‹ Tüm araçlara geri dön" + }, + "projects": { + "eyebrow": "Projeler", + "title": "Yakında", + "body": "Bir MakerLab proje galerisi hazırlanıyor — öğrenci yapımları, ders çıktıları ve ekip tarafından seçilmiş vitrinler. Şimdilik, laboratuvarda nelerin mevcut olduğunu görmek için araç kataloğuna göz atın.", + "browseTools": "Araçlara göz at" + }, + "about": { + "eyebrow": "Hakkında", + "title": "Cornell Tech MakerLab için öğrenci liderliğinde bir katalog.", + "intro": "MakerLab Tools, Cornell Tech MakerLab için dijital bir envanter ve keşif sistemidir. Öğrencilerin ekipmana göz atmasına, güvenlik belgelerini bulmasına ve laboratuvara gitmeden önce hangi fiziksel makinelerin mevcut olduğunu öğrenmesine yardımcı olur.", + "origin": "Proje bir hafta sonu prototipi olarak başladı ve mevcut Notion tabanlı kataloğa dönüştü. Araçlar, birimler, konumlar ve kaynakların tümü Notion'da bulunur ve siteyi önbellekli, salt okunur bir API katmanı üzerinden besler — böylece ekip ve öğrenci gönüllüler koda dokunmadan içeriği yönetebilir.", + "builtByHeading": "Geliştiren", + "builtByBody": "Isaac Steinberg, Johnson Cornell Tech MBA '26. MakerLab topluluğunun laboratuvar ekipmanını daha kolay bulup güvenle kullanabilmesi için öğrenci liderliğinde bir proje olarak geliştirildi.", + "howItWorksHeading": "Nasıl çalışır", + "howItWorksBody": "Ön uç, Vercel üzerinde dağıtılan bir Next.js 16 uygulamasıdır. Notion API'si katalog için doğruluk kaynağı işlevi görür; istek başına önbellek katmanı sayesinde sayfa yük altında hızlı kalır. Araç görselleri yerel olarak yansıtılır, böylece kaynaktaki imzalı URL'ler sona erse bile güvenilir şekilde görüntülenir.", + "feedbackBody": "Bir hata fark ederseniz veya bir öneriniz varsa MakerLab ekibiyle konuşun — işaretlenen düzeltmeler incelenir ve Notion'da güncellenir.", + "browseTools": "Araçlara göz at" + }, + "chat": { + "openAria": "MakerLab asistanını aç", + "title": "MAKERLAB ASİSTANI", + "newChatAria": "Yeni sohbet başlat", + "newChatTitle": "Yeni sohbet başlat", + "closeAria": "Asistanı kapat", + "closeTitle": "Kapat (sohbeti korur)", + "closeScrimAria": "MakerLab asistanını kapat", + "greetingTool": "Bu araç hakkında soru sorun — özelliklerine, malzemelerine ve kaynak bağlantılarına sahibim.", + "greetingGeneral": "Bugün size nasıl yardımcı olabilirim?", + "suggestionFindMachine": "Bir proje için makine bul", + "suggestionTraining": "Eğitim gereksinimlerini kontrol et", + "suggestionSafety": "Güvenlik veya kurallar hakkında sor", + "composerPlaceholder": "Laboratuvar konsoluna sorun...", + "composerAria": "Laboratuvar konsoluna sorun", + "sendAria": "Gönder", + "attachAria": "Fotoğraf ekle", + "attachTitle": "Fotoğraf ekle", + "removePhotoAria": "{name} öğesini kaldır", + "uploadingAria": "Fotoğraf yükleniyor", + "onlyImages": "Yalnızca görsel dosyaları desteklenir.", + "uploadFailed": "Fotoğraf yüklenemedi", + "toolRunningAria": "Araç çalışıyor", + "typingAria": "Asistan yazıyor", + "readingManualsAria": "Kılavuzlar okunuyor", + "reading": "Okunuyor: {titles}…", + "error": "Bir şeyler ters gitti. Tekrar deneyin.", + "lookingUpUnit": "🔍 Birim ayrıntıları aranıyor…", + "filingTicket": "📝 Bakım talebi oluşturuluyor…", + "working": "Üzerinde çalışılıyor…" + } +} diff --git a/v5/messages/zh-CN.json b/v5/messages/zh-CN.json new file mode 100644 index 0000000..53bbcc0 --- /dev/null +++ b/v5/messages/zh-CN.json @@ -0,0 +1,124 @@ +{ + "nav": { + "tools": "工具", + "projects": "项目", + "about": "关于", + "brandTagline": "// CORNELL TECH", + "primaryNavLabel": "主导航", + "utilityControlsLabel": "实用控件", + "themeToggleAria": "切换配色主题(系统 → 浅色 → 深色)", + "themeToggleTitle": "切换配色主题", + "languageLabel": "语言", + "languageSelectorAria": "选择语言" + }, + "status": { + "labStatusLabel": "实验室状态", + "toolsInInventory": "库存中有 {count} 件工具" + }, + "gallery": { + "title": "工具 // 机器", + "searchPlaceholder": "搜索库存…", + "searchAria": "搜索库存", + "category": "类别:", + "training": "培训:", + "viewModeLabel": "视图模式", + "grid": "[ 网格 ]", + "table": "[ 表格 ]", + "toolGalleryLabel": "工具图库", + "empty": "未找到匹配的工具。", + "loading": "正在加载库存", + "columnTool": "工具", + "columnCategory": "类别", + "columnZone": "区域", + "columnTraining": "培训", + "trainingBeginner": "初级", + "trainingIntermediate": "中级", + "trainingAdvanced": "高级" + }, + "detail": { + "breadcrumbTools": "工具", + "breadcrumbInventory": "库存", + "toolStatusLabel": "工具状态", + "trainingChip": "{level} 培训", + "ppeRequired": "需要个人防护装备", + "viewSafetyDoc": "查看安全文档", + "viewSop": "查看标准操作流程", + "atAGlanceLabel": "概览", + "materials": "材料", + "contactStaff": "请联系 MakerLab 工作人员", + "safetyAccess": "安全与使用权限", + "ppeNotice": "使用前必须佩戴个人防护装备。请查阅张贴的指南。", + "emergencyStop": "紧急停止", + "emergencyStopFallback": "遵循张贴的实验室指南,紧急情况下通知工作人员。", + "useRestrictions": "使用限制", + "useRestrictionsFallback": "在实验室开放时间内,对所有受过培训的 MakerLab 用户开放。", + "documentsResources": "文档与资源", + "openResource": "打开资源", + "resourceFallback": "资源", + "noDocuments": "尚未链接任何文档。", + "details": "详情", + "category": "类别", + "location": "位置", + "trainingRow": "培训", + "mapId": "地图编号", + "tags": "标签", + "notes": "备注", + "physicalMachines": "实体机器", + "unit": "设备", + "status": "状态", + "condition": "状况", + "serial": "序列号", + "acquired": "购置日期", + "notesTips": "备注与提示", + "backToTools": "‹ 返回所有工具" + }, + "projects": { + "eyebrow": "项目", + "title": "敬请期待", + "body": "MakerLab 项目图库正在筹备中——包括学生作品、课程成果以及工作人员精选展示。目前,请浏览工具目录,了解实验室提供的设备。", + "browseTools": "浏览工具" + }, + "about": { + "eyebrow": "关于", + "title": "面向 Cornell Tech MakerLab 的学生主导目录。", + "intro": "MakerLab Tools 是为 Cornell Tech MakerLab 打造的数字化库存与发现系统。它帮助学生浏览设备、查找安全文档,并在前往实验室之前了解哪些实体机器可用。", + "origin": "该项目最初是一个周末原型,后来发展成现在基于 Notion 的目录。工具、设备、位置和资源都存放在 Notion 中,并通过带缓存的只读 API 层向网站提供数据——因此工作人员和学生志愿者无需接触代码即可维护内容。", + "builtByHeading": "制作者", + "builtByBody": "Isaac Steinberg,Johnson Cornell Tech MBA '26。作为一个学生主导的项目而构建,旨在让 MakerLab 社区更容易找到并安全地使用实验室设备。", + "howItWorksHeading": "工作原理", + "howItWorksBody": "前端是部署在 Vercel 上的 Next.js 16 应用。Notion API 是目录的可信来源,配有按请求缓存层,使页面在高负载下保持快速。工具图片在本地镜像存储,因此即使上游签名 URL 过期也能可靠地渲染。", + "feedbackBody": "如果您发现错误或有建议,请联系 MakerLab 工作人员——标记的更正会经过审核并在 Notion 中更新。", + "browseTools": "浏览工具" + }, + "chat": { + "openAria": "打开 MakerLab 助手", + "title": "MAKERLAB 助手", + "newChatAria": "开始新对话", + "newChatTitle": "开始新对话", + "closeAria": "关闭助手", + "closeTitle": "关闭(保留对话)", + "closeScrimAria": "关闭 MakerLab 助手", + "greetingTool": "询问有关此工具的问题——我掌握它的规格、材料和资源链接。", + "greetingGeneral": "今天我能帮您什么?", + "suggestionFindMachine": "为项目寻找合适的机器", + "suggestionTraining": "查看培训要求", + "suggestionSafety": "咨询安全或政策问题", + "composerPlaceholder": "向实验室控制台提问…", + "composerAria": "向实验室控制台提问", + "sendAria": "发送", + "attachAria": "附加照片", + "attachTitle": "附加照片", + "removePhotoAria": "移除 {name}", + "uploadingAria": "正在上传照片", + "onlyImages": "仅支持图片文件。", + "uploadFailed": "照片上传失败", + "toolRunningAria": "工具运行中", + "typingAria": "助手正在输入", + "readingManualsAria": "正在阅读手册", + "reading": "正在阅读:{titles}…", + "error": "出错了,请重试。", + "lookingUpUnit": "🔍 正在查询设备详情…", + "filingTicket": "📝 正在提交维护工单…", + "working": "正在处理…" + } +} diff --git a/v5/next.config.ts b/v5/next.config.ts index c4e915b..7c730ce 100644 --- a/v5/next.config.ts +++ b/v5/next.config.ts @@ -1,4 +1,7 @@ import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; + +const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); const nextConfig: NextConfig = { cacheComponents: true, @@ -36,4 +39,4 @@ const nextConfig: NextConfig = { }, }; -export default nextConfig; +export default withNextIntl(nextConfig); diff --git a/v5/package-lock.json b/v5/package-lock.json index a0e548a..ebd20d5 100644 --- a/v5/package-lock.json +++ b/v5/package-lock.json @@ -12,6 +12,7 @@ "@ai-sdk/react": "^3.0.97", "ai": "^6.0.95", "next": "16.1.6", + "next-intl": "^4.13.0", "react": "19.2.3", "react-dom": "19.2.3", "react-markdown": "^10.1.0", @@ -152,6 +153,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -538,6 +540,36 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz", + "integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==", + "license": "MIT" + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.10.tgz", + "integrity": "sha512-XeJihYLy1lCe19xfK1KWKG/betBOK2rB0luL8lSkjfvJj0zP+LTJvkC+RKd0jsFI8mWxN71LrarHSrEXE8xxOQ==", + "license": "MIT", + "dependencies": { + "@formatjs/icu-skeleton-parser": "2.1.9" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.9.tgz", + "integrity": "sha512-rsxswgHMfU1zUgB2byc08fesf83wLGjFnzLCEtuf00mx2doiqc6pYrf67raI37XqdRcGUviQepk2UKGqpng74Q==", + "license": "MIT" + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.9.tgz", + "integrity": "sha512-GmB0F/gYh4Hdl4rLWjgDsgT+x4pB54fkJeRh8kAZ4XFzKeCK8dGs+SBJWXO42QZtOUni+IDWKNuCw6wiL4lTvw==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "3.1.5" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1334,6 +1366,313 @@ "node": ">=8.0.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1341,12 +1680,216 @@ "dev": true, "license": "MIT" }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.40.tgz", + "integrity": "sha512-PaYyclfmQ++77D8ityYvmmVzHv9aG8ROwt2GfG6/ccloy4Hgf80qtOnzb9VYvPsUT7Ty1uhuDRhv3XYpf62qhQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.40.tgz", + "integrity": "sha512-HbbPzvfLBUXjIB1Ezks+//lNUjmLjfyd63XSwprJgrZaXYdm70kohXPJUWdqKZozolFxbPaO+xtBaiUp6BoueA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.40.tgz", + "integrity": "sha512-SlRZsCjOCPR2LvFs0Ri/Xrx/5o5TCt8vl4gW6mX1hEZOG0a625RxzRHpHdAQNGykmAN/7IeaFAJG+QnNmxlHcA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.40.tgz", + "integrity": "sha512-Q8byxJt2fh8CR3EUX6snBpy47AoBVm+In/+Z3rjDHMjC38ZvR9/gtUUNCT0tfrn4EdVsO8/QPi59nxrxvqxvBQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.40.tgz", + "integrity": "sha512-4z0MgHU+7M0pZDqBN1El7mFXDI1SBwinfcUkAyA4v8QrhOIUOZltySt2aStQLZGrdXVXM4Y4ylfiTC04ED+MoQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.40.tgz", + "integrity": "sha512-fLI4iUgeSZu0eRWUXwe6YzPFx9gHbFiPkl8Rp3mJfP8OpNR3nTQCGPvHdDh9xniW7mVvgMY4ni7A4VzqI1KrpA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.40.tgz", + "integrity": "sha512-YqeKMAb7d4nQSGMJQ454IlaCENpzcDqhvBE9+CPfdnYpnUXxd+BSrB6Xk0YjW8UyoEhUj4p6quATCxbsp6J3jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.40.tgz", + "integrity": "sha512-7HOuS1iGcme/j/TuL1TfmmLGiMQrjv/GmjyZeydl00FKPtpGXEldwqfI56xgd1YzrzoB2svWjxbGGyQ0TEASxg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.40.tgz", + "integrity": "sha512-h4kZYHc7dpc9P9u4brRJaS8Pl7tPVHAeiLSzw7T5RfIJgAoSdaCMKzI/2Uay9gFhaw8uyCDl0L5q37r0EpAfIA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.40.tgz", + "integrity": "sha512-+mQgKZXSj6mV38Zh05QaxSjUDmGP/R2JWlXZTDLSPkDzHU6p3GxN9eeSf5dfyDVU86946fmCvSzyl/ucImx8+A==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.40.tgz", + "integrity": "sha512-yvwdPLGd25mcj/mNatjNQ0lZujtQD6psH3v9PNmMb+fSzjbNG8KIDxjFWrcV+fsFVLOkyOmdJsFmX7NAFjVyPw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.40.tgz", + "integrity": "sha512-OXtKsLU1bVtInzzDEAY2sYiF/rl4tvAnLLLpuMp3HzAOQZ5A+i69AKDhA1YLQTaMAqO3vzyYNVAYVRMPtSYD4w==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1356,6 +1899,15 @@ "tslib": "^2.8.0" } }, + "node_modules/@swc/types": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@tailwindcss/node": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", @@ -1715,6 +2267,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1780,6 +2333,7 @@ "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/types": "8.59.3", @@ -2320,6 +2874,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2691,6 +3246,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -3070,7 +3626,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3351,6 +3906,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3536,6 +4092,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4272,6 +4829,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/icu-minify": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.13.0.tgz", + "integrity": "sha512-SIFMeUHZJjzS5RvIGvybKvWoHjDm9cGVEs2EpJ8PmywOdJLWyblPm7TdPLLoUtkJtwQD7iGhl2WMptZ+N0on+w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^3.4.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4330,6 +4902,16 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.7.tgz", + "integrity": "sha512-+q6Ktg119nULZEpZ8YTuGOst9MyEzFtjD63FTGBlN1mLz0Z/MOUYDIvnpVKwq17eezIEh+cfJIebfJoCetpiNw==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/fast-memoize": "3.1.5", + "@formatjs/icu-messageformat-parser": "3.5.10" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -4526,7 +5108,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4572,7 +5153,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -6262,6 +6842,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "16.1.6", "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", @@ -6315,6 +6904,83 @@ } } }, + "node_modules/next-intl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.13.0.tgz", + "integrity": "sha512-OvNq2v5XLx4EkQOsAhVE9g+6zdb83XHusADCXXtIW4LILYnjEVaeINdr1lkVWKSjzwNUiMSlH5N4K0OQTRiv6A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.8.1", + "@parcel/watcher": "^2.4.1", + "@swc/core": "^1.15.2", + "icu-minify": "^4.13.0", + "negotiator": "^1.0.0", + "next-intl-swc-plugin-extractor": "^4.13.0", + "po-parser": "^2.1.1", + "use-intl": "^4.13.0" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/next-intl-swc-plugin-extractor": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.13.0.tgz", + "integrity": "sha512-6S/fJI0KXvLCL8nhBo9P8eGaJPzmwJBTCzX0NaUIj0VyU8U89d//T+vjMLdNIXl5MlLaYH7B9MbAjb8Mvu+tqQ==", + "license": "MIT" + }, + "node_modules/next-intl/node_modules/@swc/core": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.40.tgz", + "integrity": "sha512-2kwzJikRvgtNAG7MwVZY2vEzZjTxKIq5jXOihuSV/8U+Hej8Va22t65aKnJZs3P+NwojZvR8Mf8kyM7O+V8sQg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.26" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.40", + "@swc/core-darwin-x64": "1.15.40", + "@swc/core-linux-arm-gnueabihf": "1.15.40", + "@swc/core-linux-arm64-gnu": "1.15.40", + "@swc/core-linux-arm64-musl": "1.15.40", + "@swc/core-linux-ppc64-gnu": "1.15.40", + "@swc/core-linux-s390x-gnu": "1.15.40", + "@swc/core-linux-x64-gnu": "1.15.40", + "@swc/core-linux-x64-musl": "1.15.40", + "@swc/core-win32-arm64-msvc": "1.15.40", + "@swc/core-win32-ia32-msvc": "1.15.40", + "@swc/core-win32-x64-msvc": "1.15.40" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -6343,6 +7009,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -6644,6 +7316,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/po-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", + "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==", + "license": "MIT" + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -6751,6 +7429,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6760,6 +7439,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7611,6 +8291,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7793,6 +8474,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8014,6 +8696,27 @@ "punycode": "^2.1.0" } }, + "node_modules/use-intl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.13.0.tgz", + "integrity": "sha512-fAFDrWaASxlhXOipcOyb5VDD+YONqj6+8O8EcG/J7RBoOUF3A8YahRWLN+mBxYMrlMQB8N6Voqk5X+YC+HSL0A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^3.1.0", + "@schummar/icu-type-parser": "1.21.5", + "icu-minify": "^4.13.0", + "intl-messageformat": "^11.1.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -8191,6 +8894,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/v5/package.json b/v5/package.json index 0daf897..486f936 100644 --- a/v5/package.json +++ b/v5/package.json @@ -17,6 +17,7 @@ "@ai-sdk/react": "^3.0.97", "ai": "^6.0.95", "next": "16.1.6", + "next-intl": "^4.13.0", "react": "19.2.3", "react-dom": "19.2.3", "react-markdown": "^10.1.0", diff --git a/v5/src/app/about/page.tsx b/v5/src/app/about/page.tsx index 103e873..f07fb8d 100644 --- a/v5/src/app/about/page.tsx +++ b/v5/src/app/about/page.tsx @@ -1,52 +1,33 @@ import Link from "next/link"; +import { useTranslations } from "next-intl"; export const metadata = { title: "About — MakerLab Tools v5", }; export default function AboutPage() { + const t = useTranslations("about"); + return (
-

About

-

A student-led catalog for the Cornell Tech MakerLab.

- -

- MakerLab Tools is a digital inventory and discovery system for the Cornell Tech - MakerLab. It helps students browse equipment, find safety documentation, and - identify which physical machines are available in the lab before they walk over. -

- -

- The project began as a weekend prototype and grew into the current Notion-backed - catalog. Tools, units, locations, and resources all live in Notion and feed the - site through a cached read-only API layer — so staff and student volunteers can - curate content without touching code. -

- -

Built by

-

- Isaac Steinberg, Johnson Cornell Tech MBA '26. Built as a - student-led project to make it easier for the MakerLab community to find and - safely use the lab's equipment. -

- -

How it works

-

- The front end is a Next.js 16 app deployed on Vercel. The Notion API serves as - the source of truth for the catalog, with a per-request cache layer so the page - stays fast under load. Tool images are mirrored locally so they render reliably - even when upstream signed URLs expire. -

- -

- If you spot a mistake or have a suggestion, talk to the MakerLab staff — flagged - corrections are reviewed and updated in Notion. -

+

{t("eyebrow")}

+

{t("title")}

+ +

{t("intro")}

+

{t("origin")}

+ +

{t("builtByHeading")}

+

{t("builtByBody")}

+ +

{t("howItWorksHeading")}

+

{t("howItWorksBody")}

+ +

{t("feedbackBody")}

- Browse tools + {t("browseTools")}
diff --git a/v5/src/app/api/chat/route.ts b/v5/src/app/api/chat/route.ts index 8de57a0..6bec752 100644 --- a/v5/src/app/api/chat/route.ts +++ b/v5/src/app/api/chat/route.ts @@ -21,6 +21,7 @@ import { } from "../../../lib/notion"; import type { MakerLabTool, MakerLabUnit } from "../../../components/catalog-types"; import type { ResourceRecord } from "../../../lib/types"; +import { languageNameForLocale } from "../../../i18n/config"; const MAX_PDFS_PER_CHAT = 3; const MAX_PDF_BYTES = 10 * 1024 * 1024; // 10MB ceiling @@ -38,12 +39,13 @@ export const maxDuration = 60; interface ChatRequest { messages: UIMessage[]; toolId?: string; + locale?: string; } const PRIORITIES = ["Critical", "High", "Medium", "Low"] as const; export async function POST(req: Request) { - const { messages, toolId }: ChatRequest = await req.json(); + const { messages, toolId, locale }: ChatRequest = await req.json(); const tools = await getCatalogTools(); const focused = toolId ? await getCatalogTool(toolId) : null; const unitLookup = buildUnitLookup(tools); @@ -61,7 +63,7 @@ export async function POST(req: Request) { console.info(`[chat] manuals attached: ${manuals.length}`); console.info(`[chat] manuals skipped (will web_fetch): ${skipped}`); } - const system = buildSystemPrompt(tools, focused, manuals); + const system = buildSystemPrompt(tools, focused, manuals, locale); const modelMessages = attachManualsToFirstUserMessage( await convertToModelMessages(messages), manuals @@ -409,7 +411,8 @@ function attachManualsToFirstUserMessage( function buildSystemPrompt( tools: MakerLabTool[], focused: MakerLabTool | null, - manuals: AttachedManual[] = [] + manuals: AttachedManual[] = [], + locale?: string ): string { const sections: string[] = [ "You are the MakerLab Assistant — a friendly, knowledgeable helper for students using the Cornell Tech MakerLab. Answer questions about lab tools, training requirements, safety, materials, and which machines are right for a given project. Be concise, accurate, and grounded only in the catalog provided below. If the user asks about a tool that isn't in the catalog, say so honestly.", @@ -418,6 +421,13 @@ function buildSystemPrompt( `## Unit details\n\nWhen a student asks about a specific unit ("how is Prusa #1 doing?", "is Form 2 #2 working?", "show me the history on the Trotec"), call \`get_unit_details\` to fetch its live status and recent maintenance history. Surface the status, condition, and a short recap of the most recent log entries.`, ]; + if (locale && locale !== "en") { + const language = languageNameForLocale(locale); + sections.push( + `## Response language\n\nRespond to the student in **${language}**, regardless of the language they write in. Translate your explanations and conversational text into ${language}. However, ALWAYS keep the following in English so MakerLab staff can read them: tool and equipment names (use the exact catalog names), unit labels (e.g. "Prusa #1"), and — critically — the \`title\` and \`description\` you pass to the \`report_issue\` tool when filing a maintenance ticket. Maintenance ticket content must be written in English even though you reply to the student in ${language}.` + ); + } + if (focused) { sections.push( `## Active tool context\n\nThe student is currently viewing the **${focused.name}** detail page in the MakerLab catalog. If they use pronouns like "this", "it", "that tool", or "the machine", or ask things like "how do I use it" / "what can I make with this" without naming a tool, assume they are asking about the ${focused.name}. Use the resource links below when relevant — point to the SOP, safety doc, or manual when the student asks how to use, set up, or troubleshoot the tool. Do not wrap "${focused.name}" itself in a tool link — the student is already on its page.\n\n${describeTool(focused)}` diff --git a/v5/src/app/layout.tsx b/v5/src/app/layout.tsx index 9958d40..93c0563 100644 --- a/v5/src/app/layout.tsx +++ b/v5/src/app/layout.tsx @@ -1,9 +1,11 @@ import type { Metadata } from "next"; import { Suspense } from "react"; +import { NextIntlClientProvider } from "next-intl"; import "../styles/globals.css"; import { ChatFab } from "../components/ChatFab"; import { GlobalChrome } from "../components/GlobalChrome"; import { ThemeScript } from "../components/ThemeScript"; +import { LocaleHtmlScript } from "../components/LocaleHtmlScript"; import { getCatalogStats } from "../lib/catalog"; export const metadata: Metadata = { @@ -14,18 +16,39 @@ export const metadata: Metadata = { export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { const catalogStats = await getCatalogStats(); + // The shell is rendered statically (Cache Components). Locale is + // request data (a cookie), so it can't be read at the static root — instead, + // `LocaleHtmlScript` corrects `lang`/`dir` before paint, and the localized + // chrome/content streams in via Suspense boundaries below. return ( - + + - - {children} - + {children} ); } + +async function LocalizedTree({ + catalogStats, + children, +}: { + catalogStats: Awaited>; + children: React.ReactNode; +}) { + return ( + + + {children} + + + + + ); +} diff --git a/v5/src/app/projects/page.tsx b/v5/src/app/projects/page.tsx index 358d513..cbf4901 100644 --- a/v5/src/app/projects/page.tsx +++ b/v5/src/app/projects/page.tsx @@ -1,22 +1,21 @@ import Link from "next/link"; +import { useTranslations } from "next-intl"; export const metadata = { title: "Projects — MakerLab Tools v5", }; export default function ProjectsPage() { + const t = useTranslations("projects"); + return (
-

Projects

-

Coming soon

-

- A gallery of MakerLab projects is in the works — student builds, course outcomes, and - staff-curated showcases. For now, browse the tool catalog to see what's available - in the lab. -

+

{t("eyebrow")}

+

{t("title")}

+

{t("body")}

- Browse tools + {t("browseTools")}
diff --git a/v5/src/components/ChatFab.tsx b/v5/src/components/ChatFab.tsx index 6dbeb01..0e93734 100644 --- a/v5/src/components/ChatFab.tsx +++ b/v5/src/components/ChatFab.tsx @@ -5,27 +5,27 @@ import { DefaultChatTransport } from "ai"; import { useEffect, useMemo, useRef, useState } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useLocale, useTranslations } from "next-intl"; import ReactMarkdown, { type Components } from "react-markdown"; import remarkGfm from "remark-gfm"; interface Suggestion { icon: "search" | "clipboard" | "pin"; - label: string; + key: "suggestionFindMachine" | "suggestionTraining" | "suggestionSafety"; } const SUGGESTIONS: Suggestion[] = [ - { icon: "search", label: "Find a machine for a project" }, - { icon: "clipboard", label: "Check training requirements" }, - { icon: "pin", label: "Ask about safety or policy" }, + { icon: "search", key: "suggestionFindMachine" }, + { icon: "clipboard", key: "suggestionTraining" }, + { icon: "pin", key: "suggestionSafety" }, ]; -const TOOL_LABELS: Record = { - "tool-get_unit_details": "🔍 Looking up unit details…", - "tool-report_issue": "📝 Filing maintenance ticket…", -}; +type ChatT = ReturnType>; -function toolStatusLabel(partType: string): string { - return TOOL_LABELS[partType] || "Working on it…"; +function toolStatusLabel(partType: string, t: ChatT): string { + if (partType === "tool-get_unit_details") return t("lookingUpUnit"); + if (partType === "tool-report_issue") return t("filingTicket"); + return t("working"); } function Icon({ @@ -99,6 +99,8 @@ interface PendingPhoto { } export function ChatFab() { + const t = useTranslations("chat"); + const locale = useLocale(); const [isOpen, setIsOpen] = useState(false); const [draft, setDraft] = useState(""); const pathname = usePathname() || "/"; @@ -111,15 +113,20 @@ export function ChatFab() { // it (see @ai-sdk/react useChat — only `id`/`chat` prop changes recreate the // internal Chat). To make navigation update the toolId context without // resetting the conversation, we keep a stable transport that reads the - // latest toolId from a ref at send time. + // latest toolId and locale from refs at send time. const toolIdRef = useRef(toolId); useEffect(() => { toolIdRef.current = toolId; }, [toolId]); + const localeRef = useRef(locale); + useEffect(() => { + localeRef.current = locale; + }, [locale]); + const transport = useMemo( () => - // eslint-disable-next-line react-hooks/refs -- the closure below runs at send time inside an event handler, not during render. Reading toolIdRef.current there is safe. + // eslint-disable-next-line react-hooks/refs -- the closure below runs at send time inside an event handler, not during render. Reading the refs there is safe. new DefaultChatTransport({ api: "/api/chat", prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => ({ @@ -128,6 +135,7 @@ export function ChatFab() { messages, trigger, messageId, + locale: localeRef.current, ...(toolIdRef.current ? { toolId: toolIdRef.current } : {}), }, }), @@ -201,7 +209,7 @@ export function ChatFab() { setUploadError(null); const files = Array.from(fileList).filter((f) => f.type.startsWith("image/")); if (files.length === 0) { - setUploadError("Only image files are supported."); + setUploadError(t("onlyImages")); return; } @@ -239,7 +247,7 @@ export function ChatFab() { } catch (err) { revokePreview(previewUrl); const message = - err instanceof Error ? err.message : "Photo upload failed"; + err instanceof Error ? err.message : t("uploadFailed"); setUploadError(message); } finally { setUploadingCount((n) => Math.max(0, n - 1)); @@ -312,7 +320,7 @@ export function ChatFab() { type="button" aria-expanded={isOpen} aria-controls="makerlab-chat-sheet" - aria-label="Open MakerLab assistant" + aria-label={t("openAria")} onClick={() => setIsOpen(true)} > >_ @@ -323,20 +331,20 @@ export function ChatFab() { @@ -345,8 +353,8 @@ export function ChatFab() { type="button" className="chat-close" onClick={close} - aria-label="Close assistant" - title="Close (keeps conversation)" + aria-label={t("closeAria")} + title={t("closeTitle")} > @@ -357,25 +365,26 @@ export function ChatFab() { {messages.length === 0 ? ( <>

- {toolId - ? "Ask about this tool — I have its specs, materials, and resource links." - : "How can I help you today?"} + {toolId ? t("greetingTool") : t("greetingGeneral")}

- {SUGGESTIONS.map((suggestion) => ( - - ))} + {SUGGESTIONS.map((suggestion) => { + const label = t(suggestion.key); + return ( + + ); + })}
) : ( @@ -394,8 +403,8 @@ export function ChatFab() { return (
  • {textParts.length === 0 && pendingTool ? ( -

    - {toolStatusLabel(pendingTool.type)} +

    + {toolStatusLabel(pendingTool.type, t)}

    ) : null} {textParts.map((part, index) => @@ -418,11 +427,11 @@ export function ChatFab() { {isLoading && messages[messages.length - 1]?.role !== "assistant" ? (
  • {readingManuals && readingManuals.length > 0 ? ( -

    - Reading: {readingManuals.join(", ")}… +

    + {t("reading", { titles: readingManuals.join(", ") })}

    ) : ( -

    +

    @@ -432,7 +441,7 @@ export function ChatFab() { ) : null} {error ? (

  • -

    Something went wrong. Try again.

    +

    {t("error")}

  • ) : null} @@ -448,7 +457,7 @@ export function ChatFab() {