feat(i18n): install react-i18next, create English locale files, and initialize i18n framework
- Install i18next, react-i18next, i18next-browser-languagedetector - Create 6 namespace JSON files (common, collection, threads, setups, onboarding, settings) - Initialize i18n with language detection (localStorage + navigator) - Wire i18n import in main.tsx before React rendering Phase 34, Plan 01
This commit is contained in:
39
src/client/lib/i18n.ts
Normal file
39
src/client/lib/i18n.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import i18n from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
import enCollection from "../locales/en/collection.json";
|
||||
import enCommon from "../locales/en/common.json";
|
||||
import enOnboarding from "../locales/en/onboarding.json";
|
||||
import enSettings from "../locales/en/settings.json";
|
||||
import enSetups from "../locales/en/setups.json";
|
||||
import enThreads from "../locales/en/threads.json";
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: {
|
||||
en: {
|
||||
common: enCommon,
|
||||
collection: enCollection,
|
||||
threads: enThreads,
|
||||
setups: enSetups,
|
||||
onboarding: enOnboarding,
|
||||
settings: enSettings,
|
||||
},
|
||||
},
|
||||
supportedLngs: ["en", "de"],
|
||||
fallbackLng: "en",
|
||||
defaultNS: "common",
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
detection: {
|
||||
order: ["localStorage", "navigator"],
|
||||
lookupLocalStorage: "gearbox-language",
|
||||
caches: ["localStorage"],
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
31
src/client/locales/en/collection.json
Normal file
31
src/client/locales/en/collection.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"title": "Collection",
|
||||
"gear": "Gear",
|
||||
"planning": "Planning",
|
||||
"empty": {
|
||||
"title": "Your collection is empty",
|
||||
"description": "Start cataloging your gear by adding your first item. Track weight, price, and organize by category.",
|
||||
"addFirst": "Add your first item"
|
||||
},
|
||||
"form": {
|
||||
"name": "Name",
|
||||
"nameRequired": "Name *",
|
||||
"namePlaceholder": "e.g. Osprey Talon 22",
|
||||
"weight": "Weight (g)",
|
||||
"weightPlaceholder": "e.g. 680",
|
||||
"price": "Price ($)",
|
||||
"pricePlaceholder": "e.g. 129.99",
|
||||
"quantity": "Quantity",
|
||||
"category": "Category",
|
||||
"notes": "Notes",
|
||||
"notesPlaceholder": "Any additional notes...",
|
||||
"productLink": "Product Link",
|
||||
"urlPlaceholder": "https://..."
|
||||
},
|
||||
"classification": {
|
||||
"ultralight": "Ultralight",
|
||||
"light": "Light",
|
||||
"medium": "Medium",
|
||||
"heavy": "Heavy"
|
||||
}
|
||||
}
|
||||
80
src/client/locales/en/common.json
Normal file
80
src/client/locales/en/common.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "Home",
|
||||
"collection": "Collection",
|
||||
"setups": "Setups",
|
||||
"discover": "Discover",
|
||||
"settings": "Settings",
|
||||
"search": "Search",
|
||||
"searchPlaceholder": "Search catalog...",
|
||||
"profile": "Profile"
|
||||
},
|
||||
"actions": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"create": "Create",
|
||||
"close": "Close",
|
||||
"back": "Back",
|
||||
"confirm": "Confirm",
|
||||
"continue": "Continue",
|
||||
"tryAgain": "Try again",
|
||||
"dismiss": "Dismiss",
|
||||
"saving": "Saving...",
|
||||
"deleting": "Deleting...",
|
||||
"creating": "Creating...",
|
||||
"loading": "Loading...",
|
||||
"addItem": "Add Item",
|
||||
"saveChanges": "Save Changes",
|
||||
"revoke": "Revoke",
|
||||
"skipStep": "Skip this step"
|
||||
},
|
||||
"errors": {
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"unexpectedError": "An unexpected error occurred",
|
||||
"nameRequired": "Name is required",
|
||||
"positiveNumber": "Must be a positive number",
|
||||
"validUrl": "Must be a valid URL (https://...)"
|
||||
},
|
||||
"auth": {
|
||||
"signIn": "Sign in",
|
||||
"signOut": "Sign out",
|
||||
"joinGearBox": "Join GearBox",
|
||||
"signInToGearBox": "Sign in to GearBox",
|
||||
"signInDescription": "To manage your own collection, sign in or sign up.",
|
||||
"createAccount": "Create account",
|
||||
"redirectDescription": "You will be redirected to sign in with your account."
|
||||
},
|
||||
"confirm": {
|
||||
"deleteItem": "Delete Item",
|
||||
"deleteItemMessage": "Are you sure you want to delete <bold>{{name}}</bold>? This action cannot be undone.",
|
||||
"deleteCandidate": "Delete Candidate",
|
||||
"deleteCandidateMessage": "Are you sure you want to delete <bold>{{name}}</bold>? This action cannot be undone.",
|
||||
"pickWinner": "Pick Winner",
|
||||
"pickWinnerMessage": "Pick <bold>{{name}}</bold> as the winner? This will add it to your collection and archive the thread."
|
||||
},
|
||||
"externalLink": {
|
||||
"title": "You are about to leave GearBox",
|
||||
"redirectMessage": "You will be redirected to:"
|
||||
},
|
||||
"fab": {
|
||||
"addToCollection": "Add to Collection",
|
||||
"startNewThread": "Start New Thread",
|
||||
"newSetup": "New Setup"
|
||||
},
|
||||
"empty": {
|
||||
"noResults": "No results found",
|
||||
"noItems": "No items match your search"
|
||||
},
|
||||
"stats": {
|
||||
"items": "Items",
|
||||
"totalWeight": "Total Weight",
|
||||
"totalSpent": "Total Spent"
|
||||
},
|
||||
"filter": {
|
||||
"showing": "Showing {{filtered}} of {{total}} items",
|
||||
"searchItems": "Search items...",
|
||||
"allCategories": "All Categories"
|
||||
}
|
||||
}
|
||||
34
src/client/locales/en/onboarding.json
Normal file
34
src/client/locales/en/onboarding.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"welcome": {
|
||||
"title": "Welcome to GearBox",
|
||||
"subtitle": "Tell us what you're into, and we'll help you set up your collection with gear that people actually use.",
|
||||
"cta": "Let's go"
|
||||
},
|
||||
"hobby": {
|
||||
"title": "What are you into?",
|
||||
"subtitle": "Pick one or more — we'll show you popular gear for each.",
|
||||
"continue": "Continue"
|
||||
},
|
||||
"items": {
|
||||
"title": "Popular gear for {{hobby}}",
|
||||
"titleMultiple": "Popular gear for your hobbies",
|
||||
"subtitle": "Tap items you already own. We'll add them to your collection.",
|
||||
"noCatalog": "No gear cataloged yet",
|
||||
"noCatalogDescription": "We're still building our catalog for this hobby. You can skip this step and add gear manually later.",
|
||||
"reviewCount": "Review {{count}} items",
|
||||
"reviewCount_one": "Review {{count}} item"
|
||||
},
|
||||
"review": {
|
||||
"title": "Your starting collection",
|
||||
"itemsReady": "{{count}} items ready to add",
|
||||
"itemsReady_one": "{{count}} item ready to add",
|
||||
"noItemsSelected": "No items selected — you can always add gear later from the catalog.",
|
||||
"addToCollection": "Add to my collection",
|
||||
"adding": "Adding..."
|
||||
},
|
||||
"done": {
|
||||
"title": "You're all set!",
|
||||
"subtitle": "Your collection is ready. Browse the catalog anytime to discover more gear.",
|
||||
"cta": "Start exploring"
|
||||
}
|
||||
}
|
||||
32
src/client/locales/en/settings.json
Normal file
32
src/client/locales/en/settings.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"title": "Settings",
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Change the display language of the app"
|
||||
},
|
||||
"weightUnit": {
|
||||
"title": "Weight Unit",
|
||||
"description": "Choose the unit used to display weights across the app"
|
||||
},
|
||||
"currency": {
|
||||
"title": "Currency",
|
||||
"description": "Changes the currency symbol displayed. This does not convert values."
|
||||
},
|
||||
"apiKeys": {
|
||||
"title": "API Keys",
|
||||
"description": "API keys allow programmatic access to GearBox (e.g., from Claude Desktop or scripts).",
|
||||
"copyWarning": "Copy this key now — it won't be shown again:",
|
||||
"namePlaceholder": "Key name (e.g., claude-desktop)"
|
||||
},
|
||||
"importExport": {
|
||||
"title": "Import / Export",
|
||||
"description": "Export your gear collection as a CSV file, or import items from a CSV.",
|
||||
"export": "Export CSV",
|
||||
"import": "Import CSV",
|
||||
"importing": "Importing...",
|
||||
"imported": "{{count}} items imported.",
|
||||
"imported_one": "{{count}} item imported.",
|
||||
"newCategories": "New categories: {{categories}}",
|
||||
"noItemsFound": "No items found in the CSV."
|
||||
}
|
||||
}
|
||||
43
src/client/locales/en/setups.json
Normal file
43
src/client/locales/en/setups.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"title": "Setups",
|
||||
"create": "New Setup",
|
||||
"empty": {
|
||||
"title": "No setups yet",
|
||||
"description": "Create a setup to organize gear for specific trips or activities."
|
||||
},
|
||||
"card": {
|
||||
"items": "{{count}} items",
|
||||
"items_one": "{{count}} item",
|
||||
"weight": "Weight",
|
||||
"price": "Price"
|
||||
},
|
||||
"share": {
|
||||
"title": "Share Setup",
|
||||
"shareLinks": "Share Links",
|
||||
"createLink": "Create Link",
|
||||
"noLinks": "No share links yet",
|
||||
"copyLink": "Copy link",
|
||||
"revokeLink": "Revoke link",
|
||||
"copied": "Copied!",
|
||||
"noExpiration": "No expiration",
|
||||
"expired": "Expired",
|
||||
"expiresToday": "Expires today",
|
||||
"expiresTomorrow": "Expires tomorrow",
|
||||
"expiresInDays": "Expires in {{days}} days",
|
||||
"daysOption": "{{days}} days",
|
||||
"deactivateWarning": "Switching to private will deactivate all share links. They can be reactivated by switching back."
|
||||
},
|
||||
"visibility": {
|
||||
"private": "Private",
|
||||
"privateDescription": "Only you can access",
|
||||
"link": "Link sharing",
|
||||
"linkDescription": "Anyone with the link",
|
||||
"public": "Public",
|
||||
"publicDescription": "Visible on your profile"
|
||||
},
|
||||
"impact": {
|
||||
"title": "Impact Preview",
|
||||
"adding": "Adding",
|
||||
"removing": "Removing"
|
||||
}
|
||||
}
|
||||
45
src/client/locales/en/threads.json
Normal file
45
src/client/locales/en/threads.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"title": "Research Threads",
|
||||
"create": {
|
||||
"title": "New Thread",
|
||||
"threadName": "Thread name",
|
||||
"namePlaceholder": "e.g. Lightweight sleeping bag",
|
||||
"category": "Category",
|
||||
"nameRequired": "Thread name is required",
|
||||
"selectCategory": "Please select a category",
|
||||
"createFailed": "Failed to create thread",
|
||||
"createThread": "Create Thread"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"researching": "Researching",
|
||||
"ordered": "Ordered",
|
||||
"arrived": "Arrived",
|
||||
"resolved": "Resolved",
|
||||
"archived": "Archived"
|
||||
},
|
||||
"candidate": {
|
||||
"name": "Name",
|
||||
"price": "Price",
|
||||
"weight": "Weight",
|
||||
"url": "URL",
|
||||
"pros": "Pros",
|
||||
"cons": "Cons",
|
||||
"notes": "Notes",
|
||||
"addCandidate": "Add Candidate"
|
||||
},
|
||||
"comparison": {
|
||||
"weight": "Weight",
|
||||
"price": "Price",
|
||||
"pros": "Pros",
|
||||
"cons": "Cons"
|
||||
},
|
||||
"resolve": {
|
||||
"title": "Pick Winner",
|
||||
"message": "Pick <bold>{{name}}</bold> as the winner? This will add it to your collection and archive the thread."
|
||||
},
|
||||
"empty": {
|
||||
"noThreads": "No research threads yet",
|
||||
"noCandidates": "No candidates yet"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import "./lib/i18n";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { createRouter, RouterProvider } from "@tanstack/react-router";
|
||||
import { StrictMode } from "react";
|
||||
|
||||
Reference in New Issue
Block a user