diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 4c7169ff..53d7e058 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -20,3 +20,4 @@ Detailed instructions are organized in the rules directory: @.claude/rules/code-style.md - Code style, formatting, and quality requirements @.claude/rules/workflow.md - Git workflow, branches, PRs, and issues @.claude/rules/patterns.md - Important coding patterns for this codebase +@.claude/rules/i18n.md - Internationalization guidelines and best practices diff --git a/.claude/rules/code-style.md b/.claude/rules/code-style.md index 8a00cae9..33dfdabb 100644 --- a/.claude/rules/code-style.md +++ b/.claude/rules/code-style.md @@ -20,6 +20,16 @@ - **React 19** with functional components and hooks - No class components +## Internationalization (i18n) + +- **NEVER hardcode user-facing strings** - All text visible to users must use the i18n system +- Use `useTranslation()` hook from react-i18next in all components with user-facing text +- See @.claude/rules/i18n.md for complete guidelines on: + - Choosing the right namespace + - Adding new translation keys + - Variable interpolation and pluralization + - Supporting new languages + ## CSS - All styles should be in `src/styles` folder @@ -49,7 +59,11 @@ npm run lint:fix # 3. Verify type safety npm run typecheck -# 4. Run tests (if applicable) +# 4. Verify i18n compliance +# - Ensure no hardcoded user-facing strings +# - Test in both English and Spanish if you added translations + +# 5. Run tests (if applicable) npm run test:run ``` diff --git a/.claude/rules/i18n.md b/.claude/rules/i18n.md new file mode 100644 index 00000000..66948ff5 --- /dev/null +++ b/.claude/rules/i18n.md @@ -0,0 +1,341 @@ +# Internationalization (i18n) + +## Core Principle + +**NEVER hardcode user-facing strings.** All text visible to users must use the i18n system. + +## Library and Configuration + +- **Library**: react-i18next (v16.5.4) with i18next (v25.8.0) +- **Configuration**: `src/i18n.ts` +- **Type Definitions**: `src/i18next.d.ts` (provides TypeScript autocomplete) +- **Supported Languages**: English (en), Spanish (es) +- **Language Detection**: Auto-detects from browser or localStorage (`openScan_language`) + +## When to Use i18n + +✅ **ALWAYS use i18n for**: +- UI labels and buttons +- Page titles and headings +- Error messages +- Success/info messages +- Form labels and placeholders +- Tooltips and aria-labels +- Toast notifications +- Modal content +- Loading states +- Empty states + +❌ **DO NOT use i18n for**: +- Code comments +- Console.log statements +- Developer-only debug messages +- Variable names +- URLs (unless they're user-facing link text) +- File paths +- API endpoints + +## Available Namespaces + +Choose the appropriate namespace based on the component location: + +1. **common** - Navigation, footer, shared UI elements, general errors, wallet, RPC indicator +2. **home** - Home page content +3. **settings** - Settings page (appearance, language, cache, RPC, API keys) +4. **address** - Address page (contract verification, tokens, NFTs, transactions, ENS) +5. **block** - Blocks and Block details page +6. **transaction** - Transactions and Transaction details page (status, gas, events, traces, L2 fields) +7. **network** - Network statistics and information +8. **devtools** - Developer tools page +9. **errors** - Error messages (if not in common) +10. **tokenDetails** - Token detail pages (ERC20, ERC721, ERC1155) + +## Basic Usage + +### Import and Setup + +```tsx +import { useTranslation } from "react-i18next"; + +const MyComponent = () => { + // Default namespace (common) + const { t } = useTranslation(); + + // Specific namespace + const { t } = useTranslation("settings"); + + // Both t function and i18n instance + const { t, i18n } = useTranslation("settings"); +}; +``` + +### Simple Translation + +```tsx +// ❌ BAD - Hardcoded + + +// ✅ GOOD - Using i18n +const { t } = useTranslation("common"); + +``` + +### Nested Keys + +```tsx +// Translation file: src/locales/en/settings.json +{ + "cacheData": { + "clearSiteData": { + "confirmMessage": "This will clear all data. Continue?" + } + } +} + +// Usage: Dot notation for nested keys +const { t } = useTranslation("settings"); +const message = t("cacheData.clearSiteData.confirmMessage"); +``` + +## Variable Interpolation + +Use `{{variableName}}` syntax for dynamic values: + +```tsx +// Translation file +{ + "footer": { + "versionLabel": "v{{version}} {{hash}}" + } +} + +// Usage +const { t } = useTranslation("common"); +{t("footer.versionLabel", { version: "1.0.0", hash: "abc123" })} +// Output: "v1.0.0 abc123" +``` + +## Pluralization + +Use `_other` suffix for plural forms: + +```tsx +// Translation file +{ + "item": "{{count}} item", + "item_other": "{{count}} items" +} + +// Usage +const { t } = useTranslation(); +
{t("item", { count: 1 })}
// "1 item" +{t("item", { count: 5 })}
// "5 items" +``` + +## Language Switching + +```tsx +const { i18n } = useTranslation(); + +// Change language +const switchLanguage = (langCode: string) => { + i18n.changeLanguage(langCode); + localStorage.setItem("openScan_language", langCode); +}; + +// Get current language +const currentLang = i18n.language; // "en" or "es" +``` + +## Adding New Translation Keys + +### Step-by-step Process + +1. **Identify the appropriate namespace** based on component location +2. **Add key to English file** (`src/locales/en/{namespace}.json`) +3. **Add same key to Spanish file** (`src/locales/es/{namespace}.json`) +4. **Use TypeScript autocomplete** to verify the key exists +5. **Test in both languages** by switching in Settings + +### Example + +```bash +# 1. Add to English file +# src/locales/en/address.json +{ + "contractVerified": "Contract Verified", + "newKey": "Your new text here" # ← Add this +} + +# 2. Add to Spanish file +# src/locales/es/address.json +{ + "contractVerified": "Contrato Verificado", + "newKey": "Tu nuevo texto aquí" # ← Add this +} + +# 3. Use in component +const { t } = useTranslation("address"); +{t("newKey")} # TypeScript autocomplete will suggest "newKey" +``` + +## TypeScript Type Safety + +The i18n system provides full TypeScript autocomplete and type checking: + +```tsx +const { t } = useTranslation("settings"); + +// ✅ Valid - TypeScript knows this key exists +t("language.title"); + +// ❌ Invalid - TypeScript error: key doesn't exist +t("nonexistent.key"); // Type error! +``` + +**Important**: If a translation key doesn't autocomplete, it doesn't exist in the type definitions! + +## Common Patterns + +### Aria Labels and Accessibility + +```tsx +// ❌ BAD + + +// ✅ GOOD +const { t } = useTranslation("common"); + +``` + +### Conditional Messages + +```tsx +const { t } = useTranslation("common"); +const status = isConnected + ? t("rpcIndicator.connected") + : t("rpcIndicator.disconnected"); +``` + +### Loading States + +```tsx +// ❌ BAD +{t("appearance.description")}
+ + > + ); +}; +``` + +### Bad Example - Hardcoded Strings (Don't do this!) + +```tsx +// ❌ WRONG - Multiple hardcoded strings +- We're sorry, but something unexpected happened. The application encountered an - error. -
+{i18next.t("errors.errorMessage")}
{this.state.error.toString()}
{this.state.errorInfo && (
<>
- {this.state.errorInfo.componentStack}
>
)}
@@ -109,7 +108,7 @@ export const useErrorHandler = () => {
}, []);
const captureError = React.useCallback((error: Error) => {
- console.error("Error captured:", error);
+ logger.error("Error captured:", error);
setError(error);
}, []);
diff --git a/src/components/common/ExtraDataDisplay.tsx b/src/components/common/ExtraDataDisplay.tsx
index 2316ee0d..6848bf14 100644
--- a/src/components/common/ExtraDataDisplay.tsx
+++ b/src/components/common/ExtraDataDisplay.tsx
@@ -1,5 +1,6 @@
import type React from "react";
import { useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
interface ExtraDataDisplayProps {
hexData: string;
@@ -7,6 +8,7 @@ interface ExtraDataDisplayProps {
}
const ExtraDataDisplay: React.FC