diff --git a/package-lock.json b/package-lock.json index 6e28250..1c1f31a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,11 @@ "name": "dsa5e-character-sheet-symbiote", "version": "0.0.0", "dependencies": { + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-i18next": "^15.5.3", "react-icons": "^5.5.0" }, "devDependencies": { @@ -264,6 +267,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -2426,6 +2438,55 @@ "node": ">=8" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz", + "integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2898,6 +2959,32 @@ "react": "^19.1.0" } }, + "node_modules/react-i18next": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.3.tgz", + "integrity": "sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -3172,7 +3259,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -3349,6 +3436,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index ef12acf..86ae0b3 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-i18next": "^15.5.3", "react-icons": "^5.5.0" }, "devDependencies": { diff --git a/src/components/Attributes.tsx b/src/components/Attributes.tsx index c3417fe..c0fbbc2 100644 --- a/src/components/Attributes.tsx +++ b/src/components/Attributes.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import type {DSACharacter, DSAAttributes} from '../types/character'; interface AttributesProps { @@ -7,6 +8,7 @@ interface AttributesProps { } const Attributes: React.FC = ({ character, setCharacter }) => { + const { t } = useTranslation(); const updateAttribute = (attr: keyof DSAAttributes, value: number) => { // Input validation - clamp between 1 and 20 const clampedValue = Math.min(20, Math.max(1, value)); @@ -20,25 +22,18 @@ const Attributes: React.FC = ({ character, setCharacter }) => { })); }; - const attributeDisplayNames: Record = { - courage: "CO - Courage", - cleverness: "CL - Cleverness", - intuition: "IN - Intuition", - charisma: "CH - Charisma", - dexterity: "DE - Dexterity", - agility: "AG - Agility", - constitution: "CN - Constitution", - strength: "ST - Strength" + const getAttributeDisplayName = (attr: keyof DSAAttributes): string => { + return t(`attributes.${attr}`); }; return (
-

Attributes

+

{t('attributes.title')}

{Object.entries(character.attributes).map(([key, value]) => (
= ({ character, setCharacter }) => { + const { t } = useTranslation(); const updateField = (field: keyof DSACharacter, value: string) => { setCharacter(prev => ({ ...prev, @@ -16,50 +18,50 @@ const BasicInfo: React.FC = ({ character, setCharacter }) => { return (
-

Basic Information

+

{t('basicInfo.title')}

- + updateField('name', e.target.value)} - placeholder="Enter character name" + placeholder={t('basicInfo.namePlaceholder')} />
- + updateField('species', e.target.value)} - placeholder="e.g., Human, Elf, Dwarf" + placeholder={t('basicInfo.speciesPlaceholder')} />
- + updateField('culture', e.target.value)} - placeholder="e.g., Middenrealmish, Thorwalian" + placeholder={t('basicInfo.culturePlaceholder')} />
- + updateField('profession', e.target.value)} - placeholder="e.g., Warrior, Mage, Scout" + placeholder={t('basicInfo.professionPlaceholder')} />
- +