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:
2026-04-13 18:13:55 +02:00
parent de82eefa74
commit 8c0fb31df2
10 changed files with 323 additions and 0 deletions

View File

@@ -17,11 +17,14 @@
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"framer-motion": "^12.38.0", "framer-motion": "^12.38.0",
"hono": "^4.12.8", "hono": "^4.12.8",
"i18next": "^26.0.4",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.577.0", "lucide-react": "^0.577.0",
"postgres": "^3.4.9", "postgres": "^3.4.9",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-easy-crop": "^5.5.7", "react-easy-crop": "^5.5.7",
"react-i18next": "^17.0.2",
"recharts": "^3.8.0", "recharts": "^3.8.0",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"sonner": "^2.0.7", "sonner": "^2.0.7",
@@ -166,6 +169,8 @@
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
@@ -794,8 +799,14 @@
"hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="], "hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="],
"html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"i18next": ["i18next@26.0.4", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-gXF7U9bfioXPLv7mw8Qt2nfO7vij5MyINvPgVv99pX3fL1Y01pw2mKBFrlYpRxRCl2wz3ISenj6VsMJT2isfuA=="],
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.1", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
@@ -964,6 +975,8 @@
"react-easy-crop": ["react-easy-crop@5.5.7", "", { "dependencies": { "normalize-wheel": "^1.0.1", "tslib": "^2.0.1" }, "peerDependencies": { "react": ">=16.4.0", "react-dom": ">=16.4.0" } }, "sha512-kYo4NtMeXFQB7h1U+h5yhUkE46WQbQdq7if54uDlbMdZHdRgNehfvaFrXnFw5NR1PNoUOJIfTwLnWmEx/MaZnA=="], "react-easy-crop": ["react-easy-crop@5.5.7", "", { "dependencies": { "normalize-wheel": "^1.0.1", "tslib": "^2.0.1" }, "peerDependencies": { "react": ">=16.4.0", "react-dom": ">=16.4.0" } }, "sha512-kYo4NtMeXFQB7h1U+h5yhUkE46WQbQdq7if54uDlbMdZHdRgNehfvaFrXnFw5NR1PNoUOJIfTwLnWmEx/MaZnA=="],
"react-i18next": ["react-i18next@17.0.2", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.0.1", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA=="],
"react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="], "react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="],
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
@@ -1102,6 +1115,8 @@
"vite": ["vite@8.0.0", "", { "dependencies": { "@oxc-project/runtime": "0.115.0", "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.9", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.0.0-alpha.31", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q=="], "vite": ["vite@8.0.0", "", { "dependencies": { "@oxc-project/runtime": "0.115.0", "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.9", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.0.0-alpha.31", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q=="],
"void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="],
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],

View File

@@ -48,11 +48,14 @@
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"framer-motion": "^12.38.0", "framer-motion": "^12.38.0",
"hono": "^4.12.8", "hono": "^4.12.8",
"i18next": "^26.0.4",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.577.0", "lucide-react": "^0.577.0",
"postgres": "^3.4.9", "postgres": "^3.4.9",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-easy-crop": "^5.5.7", "react-easy-crop": "^5.5.7",
"react-i18next": "^17.0.2",
"recharts": "^3.8.0", "recharts": "^3.8.0",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"sonner": "^2.0.7", "sonner": "^2.0.7",

39
src/client/lib/i18n.ts Normal file
View 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;

View 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"
}
}

View 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"
}
}

View 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"
}
}

View 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."
}
}

View 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"
}
}

View 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"
}
}

View File

@@ -1,3 +1,4 @@
import "./lib/i18n";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createRouter, RouterProvider } from "@tanstack/react-router"; import { createRouter, RouterProvider } from "@tanstack/react-router";
import { StrictMode } from "react"; import { StrictMode } from "react";