Skip to content

A lightweight, professional localization package for React and React Native applications built on top of i18next and react-i18next. This package provides a clean, type-safe API for managing translations with built-in language switching, variable injection, and component interpolation capabilities.

Notifications You must be signed in to change notification settings

weprodev/ui-localization

Repository files navigation

@weprodev/ui-localization

A lightweight, professional localization package for React and React Native applications built on top of i18next and react-i18next. This package provides a clean, type-safe API for managing translations with built-in language switching, variable injection, and component interpolation capabilities.

✨ Features

  • 🌐 Simple React Hooks - Clean, intuitive hooks for translations and language management
  • πŸ”„ Language Switching - Built-in language switching with persistence
  • πŸ“ Type-Safe Variable Injection - Dynamic content insertion with compile-time parameter validation
  • 🧩 Unified Component Interpolation - Embed React components within translations using the same t function
  • πŸ” Translation Validation - CLI tools to ensure translation consistency
  • πŸ”„ Translation Sync - Automated synchronization of translation files
  • πŸ“± React Native Support - Full compatibility with React Native applications
  • πŸ›‘οΈ Full Type Safety - TypeScript support with type-safe keys, parameters, and component interpolation
  • ⚑ Performance Optimized - Lightweight wrapper with minimal overhead

πŸ“¦ Installation

npm install @weprodev/ui-localization

πŸš€ Quick Start

React Applications

1. Create Translation Files

Create a translations directory in your project with language files. Important: Use as const to enable type-safe parameter validation:

// translations/en.ts
const en = {
  common: {
    hello: "Hello {{name}}!",
    welcome: "Welcome {{name}}!",
    goodbye: "Goodbye {{name}}!",
    greeting: "Hello, {{name}}!"
  },
  auth: {
    login: "Login",
    signup: "Sign Up",
    forgotPassword: "Forgot Password",
    welcomeMessage: "Welcome <strong>{{name}}</strong>! Please <link>sign in</link> to continue."
  },
  dashboard: {
    title: "Dashboard",
    summary: "Summary",
    recentActivity: "Recent Activity"
  }
} as const;

export default en;
// translations/es.ts
const es = {
  common: {
    hello: "Hola {{name}}!",
    welcome: "Bienvenido {{name}}!",
    goodbye: "AdiΓ³s {{name}}!",
    greeting: "Β‘Hola, {{name}}!"
  },
  auth: {
    login: "Iniciar sesiΓ³n",
    signup: "Registrarse",
    forgotPassword: "ContraseΓ±a olvidada",
    welcomeMessage: "Bienvenido <strong>{{name}}</strong>! Por favor <link>inicia sesiΓ³n</link> para continuar."
  },
  dashboard: {
    title: "Panel de control",
    summary: "Resumen",
    recentActivity: "Actividad reciente"
  }
} as const;

export default es;

2. Create Localization Configuration

// src/localizationConfig.ts
import { LocalizationConfig, LanguageStore } from '@weprodev/ui-localization';
import en from '../translations/en';
import es from '../translations/es';

// Optional: Create a custom language store for persistence
class CustomLanguageStore implements LanguageStore {
  getLanguage(): string | null {
    return localStorage.getItem("app-language") || null;
  }

  setLanguage(language: string): void {
    localStorage.setItem("app-language", language);
  }
}

export const localizationConfig: LocalizationConfig = {
  resources: {
    en: { translation: en },
    es: { translation: es }
  },
  fallbackLng: 'en',
  languageStore: new CustomLanguageStore()
};

3. Initialize Localization

// src/index.tsx
import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { initLocalization } from '@weprodev/ui-localization';
import { localizationConfig } from './localizationConfig';
import App from './App';

const rootElement = document.getElementById('root');

// Initialize localization before rendering the app
initLocalization(localizationConfig).then(() => {
  ReactDOM.createRoot(rootElement).render(
    <StrictMode>
      <App />
    </StrictMode>
  );
});

4. Use Translations in Components

// src/components/Welcome.tsx
import React from 'react';
import { useTranslation } from '@weprodev/ui-localization';
import en from '../translations/en';

const Welcome: React.FC<{ name: string }> = ({ name }) => {
  // Type-safe translation hook with path-based keys
  const { t } = useTranslation<typeof en>();
  
  return (
    <div>
      {/* TypeScript requires 'name' parameter because translation has {{name}} placeholder */}
      <h1>{t('common.welcome', { name })}</h1>
      <p>{t('common.hello', { name })}</p>
      {/* TypeScript will error if you use invalid keys like t('common.invalid') */}
      {/* TypeScript will error if you forget required parameters */}
    </div>
  );
};

export default Welcome;

Alternative: Create a custom hook for better reusability

// src/hooks/useAppTranslation.ts
import { useTranslation } from '@weprodev/ui-localization';
import en from '../translations/en';

export const useAppTranslation = () => {
  return useTranslation<typeof en>();
};

// Usage in components
import { useAppTranslation } from '../hooks/useAppTranslation';

const Welcome: React.FC = () => {
  const { t } = useAppTranslation();
  
  return (
    <div>
      <h1>{t('common.welcome')}</h1> {/* Full intellisense and type safety */}
      <p>{t('common.hello')}</p>
      {/* t('common.invalid') will show TypeScript error */}
    </div>
  );
};

Important: For type-safe parameter validation to work, your translation files must use as const. This ensures TypeScript preserves literal string types, allowing the system to extract parameter names from placeholders like {{name}}.

Troubleshooting: If you encounter TypeScript errors with the type-safe hook, you can use useTranslationFallback() as an escape hatch. See the API Reference for details.

5. Language Switching

// src/components/LanguageSwitcher.tsx
import React from 'react';
import { useLanguage } from '@weprodev/ui-localization';

const LanguageSwitcher: React.FC = () => {
  const { currentLanguage, changeLanguage, availableLanguages } = useLanguage();
  
  return (
    <select 
      value={currentLanguage} 
      onChange={(e) => changeLanguage(e.target.value)}
    >
      {availableLanguages.map(lang => (
        <option key={lang} value={lang}>
          {lang.toUpperCase()}
        </option>
      ))}
    </select>
  );
};

export default LanguageSwitcher;

React Native Applications

1. Create Translation Files

Same structure as React applications (see above).

2. Create Localization Configuration

// src/localizationConfig.ts
import { LocalizationConfig, LanguageStore } from '@weprodev/ui-localization';
import AsyncStorage from '@react-native-async-storage/async-storage';
import en from '../translations/en';
import es from '../translations/es';

// React Native language store using AsyncStorage
class ReactNativeLanguageStore implements LanguageStore {
  async getLanguage(): Promise<string | null> {
    try {
      return await AsyncStorage.getItem("app-language");
    } catch {
      return null;
    }
  }

  async setLanguage(language: string): Promise<void> {
    try {
      await AsyncStorage.setItem("app-language", language);
    } catch {
      // Handle storage error silently
    }
  }
}

export const localizationConfig: LocalizationConfig = {
  resources: {
    en: { translation: en },
    es: { translation: es }
  },
  fallbackLng: 'en',
  languageStore: new ReactNativeLanguageStore()
};

3. Initialize Localization

// src/App.tsx
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import { initLocalization } from '@weprodev/ui-localization';
import { localizationConfig } from './localizationConfig';

const App: React.FC = () => {
  const [isInitialized, setIsInitialized] = useState(false);

  useEffect(() => {
    initLocalization(localizationConfig).then(() => {
      setIsInitialized(true);
    });
  }, []);

  if (!isInitialized) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Loading...</Text>
      </View>
    );
  }

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      {/* Your app content */}
    </View>
  );
};

export default App;

4. Use Translations in React Native Components

// src/components/Welcome.tsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useTranslation } from '@weprodev/ui-localization';
import en from '../translations/en';

const Welcome: React.FC<{ name: string }> = ({ name }) => {
  // Type-safe translation hook with path-based keys
  const { t } = useTranslation<typeof en>();
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>{t('common.welcome', { name })}</Text>
      <Text style={styles.subtitle}>{t('common.hello', { name })}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
  },
});

export default Welcome;

5. Language Switching in React Native

// src/components/LanguageSwitcher.tsx
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useLanguage } from '@weprodev/ui-localization';

const LanguageSwitcher: React.FC = () => {
  const { currentLanguage, changeLanguage, availableLanguages } = useLanguage();
  
  return (
    <View style={styles.container}>
      <Text style={styles.label}>Language:</Text>
      <View style={styles.buttonContainer}>
        {availableLanguages.map(lang => (
          <TouchableOpacity
            key={lang}
            style={[
              styles.button,
              currentLanguage === lang && styles.activeButton
            ]}
            onPress={() => changeLanguage(lang)}
          >
            <Text style={[
              styles.buttonText,
              currentLanguage === lang && styles.activeButtonText
            ]}>
              {lang.toUpperCase()}
            </Text>
          </TouchableOpacity>
        ))}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  label: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  buttonContainer: {
    flexDirection: 'row',
    gap: 10,
  },
  button: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 4,
    borderWidth: 1,
    borderColor: '#ddd',
  },
  activeButton: {
    backgroundColor: '#007AFF',
    borderColor: '#007AFF',
  },
  buttonText: {
    fontSize: 14,
    color: '#333',
  },
  activeButtonText: {
    color: '#fff',
  },
});

export default LanguageSwitcher;

πŸ”§ Advanced Usage

Translation with Variables (Type-Safe Parameters)

The t function enforces type-safe parameters based on placeholders in your translation strings. Parameters are required when placeholders exist, and optional when they don't.

import { useTranslation } from '@weprodev/ui-localization';
import en from '../translations/en';

const Greeting: React.FC<{ name: string }> = ({ name }) => {
  const { t } = useTranslation<typeof en>();
  
  // Translation: "common.greeting": "Hello, {{name}}!"
  // βœ… TypeScript requires the 'name' parameter
  const greeting = t('common.greeting', { name });
  
  // ❌ TypeScript error: missing required parameter 'name'
  // const greeting = t('common.greeting');
  
  // ❌ TypeScript error: wrong parameter name
  // const greeting = t('common.greeting', { wrongName: name });
  
  // βœ… No parameters needed for translations without placeholders
  const title = t('dashboard.title');
  
  return <p>{greeting}</p>;
};

Translation with Component Interpolation

The unified t function supports both string and component interpolation. When you pass a components object as the third argument, it returns a React element instead of a string.

import { useTranslation } from '@weprodev/ui-localization';
import en from '../translations/en';

const WelcomeMessage: React.FC<{ name: string }> = ({ name }) => {
  const { t } = useTranslation<typeof en>();
  
  // Translation: "auth.welcomeMessage": "Welcome <strong>{{name}}</strong>! Please <link>sign in</link> to continue."
  // The translation string must contain matching HTML-like tags (<strong>, <link>, etc.)
  const welcomeElement = t(
    'auth.welcomeMessage',
    { name },
    {
      strong: <strong className="highlight" />,
      link: (props: { children?: React.ReactNode }) => (
        <a href="#login" className="link-button">
          {props.children}
        </a>
      )
    }
  );
  
  // Returns JSX.Element when components are provided
  return <div>{welcomeElement}</div>;
};

Important Notes:

  • Component interpolation only works when the translation string contains matching HTML-like tags (e.g., <strong>, <link>)
  • The component keys in your components object must match the tag names in the translation
  • For self-closing tags like <strong />, use self-closing components
  • For tags with content like <link>text</link>, use function components that accept props.children

Usage Outside React Components

For utility functions or other non-component files where hooks are not available, you can use createTranslation to get a type-safe t function.

import { createTranslation } from '@weprodev/ui-localization';
import en from '../translations/en';

// Create a standalone translation function
const t = createTranslation<typeof en>();

// Basic usage - Returns string
const greeting = t('common.hello');

// With parameters - Returns string
const message = t('common.welcome', { name: 'World' });

// With components - Returns JSX.Element
// Useful for creating localized constants with React elements
const content = t('common.info', undefined, {
  link: <a href="/more">More</a>
});

πŸ› οΈ Translation Management Tools

The package includes powerful CLI tools to help manage your translations. You can use these tools in multiple ways:

Option 1: Using NPM Scripts (Recommended)

Add these commands to your project's package.json scripts:

{
  "scripts": {
    "translation:validate": "wpd-translation-validate --dir ./translations --source en",
    "translation:sync": "wpd-translation-sync --dir ./translations --source en"
  }
}

Then run:

npm run translation:validate
npm run translation:sync

Customizing paths:

Modify the scripts in your package.json to use different directories or source language:

{
  "scripts": {
    "translation:validate": "wpd-translation-validate --dir ./src/translations --source es",
    "translation:sync": "wpd-translation-sync --dir ./src/translations --source es"
  }
}

Option 2: Using npx (Direct CLI)

Run the CLI tools directly via npx:

# Validate translations
npx wpd-translation-validate --dir ./translations --source en

# Sync translations
npx wpd-translation-sync --dir ./translations --source en

What These Tools Do

Validate Translations (validate-translations)

  • Checks if all language files have the same keys as the source language
  • Reports missing keys for each language file
  • Exits with error code if inconsistencies are found
  • Perfect for CI/CD integration

Sync Translations (sync-translations)

  • Adds missing keys from the source language to all other language files
  • Preserves existing translations
  • Sets empty string values for new keys (ready for translation)
  • Updates translation files automatically

CI/CD Integration

We recommend running translation:validate as part of your CI pipeline to ensure translation consistency:

# .github/workflows/ci.yml
- name: Validate Translations
  run: npm run translation:validate

πŸ“š API Reference

Hooks

useTranslation<T>()

Returns a type-safe translation function with intellisense support and type-safe parameter validation.

Parameters:

  • None (the translation object type T is provided as a generic parameter for type safety)

Returns: Object with t function that accepts type-safe translation keys

The t function supports two modes:

  1. String interpolation (returns string):
import en from '../translations/en';

const { t } = useTranslation<typeof en>();

// No parameters needed for translations without placeholders
const title = t('dashboard.title'); // βœ… Returns string

// Parameters required when placeholders exist
const greeting = t('common.greeting', { name: 'John' }); // βœ… Returns string
const greeting = t('common.greeting'); // ❌ TypeScript error: missing required parameter
  1. Component interpolation (returns JSX.Element):
// Pass components as third argument
const welcomeElement = t(
  'auth.welcomeMessage',
  { name: 'Alice' },
  {
    strong: <strong className="highlight" />,
    link: (props) => <a href="#login">{props.children}</a>
  }
); // βœ… Returns JSX.Element

Type-Safe Parameters:

  • Parameters are required when the translation string contains placeholders like {{name}}
  • Parameters are optional when the translation has no placeholders
  • TypeScript validates parameter names match the placeholders in the translation

Note: For better reusability, consider creating a custom hook:

// src/hooks/useAppTranslation.ts
import { useTranslation } from '@weprodev/ui-localization';
import en from '../translations/en';

export const useAppTranslation = () => {
  return useTranslation<typeof en>();
};

// Usage
const { t } = useAppTranslation();
const text = t('common.welcome', { name: 'User' }); // Full type safety and intellisense

useLanguage()

Provides language management functionality.

const { currentLanguage, changeLanguage, availableLanguages } = useLanguage();

useTranslationFallback()

⚠️ Escape hatch hook - use only when the main useTranslation hook has TypeScript issues.

Returns the raw i18next translation function without type safety. This should only be used in rare edge cases where the type-safe hook encounters problems.

// Only use when useTranslation has TypeScript errors
const t = useTranslationFallback();
const text = t('common.hello'); // No type safety - standard i18next usage
const withVars = t('greeting', { name: 'John' });

Note: The main useTranslation hook should be preferred in 99% of cases.

Core Functions

initLocalization(config)

Initializes the localization system.

await initLocalization({
  resources: { en: { translation: enTranslations } },
  fallbackLng: 'en',
  languageStore: new CustomLanguageStore()
});

Types

LanguageStore

Interface for custom language storage implementations.

interface LanguageStore {
  getLanguage(): string | null;
  setLanguage(language: string): void;
}

LocalizationConfig

Configuration object for localization initialization.

interface LocalizationConfig {
  resources: Resource;
  fallbackLng?: string;
  compatibilityJSON?: "v4";
  interpolation?: {
    escapeValue?: boolean;
  };
  languageStore?: LanguageStore;
}

ComponentMap

Type for component interpolation map. Supports both React elements and function components.

type ComponentMap = {
  [key: string]: 
    | React.ReactElement
    | ((props: { children?: React.ReactNode; [key: string]: any }) => React.ReactElement)
}

TranslateFunction<T>

Type definition for the type-safe translation function.

type TranslateFunction<T extends NestedRecord>

UseTranslationReturn<T>

Return type of the useTranslation hook.

interface UseTranslationReturn<T extends NestedRecord> {
  t: TranslateFunction<T>;
}

NestedRecord

Base type for translation resources, allowing recursive nesting of primitive values.

type NestedRecord = { 
  [key: string]: string | number | boolean | null | undefined | NestedRecord 
};

πŸ†˜ Support

For support, bug reports, or feature requests, please contact the WeProDev team or create an issue in our internal repository.

πŸ“„ License

This project is licensed under the MIT License.

πŸ”— Links


@weprodev/ui-localization - Professional localization solution by WeProDev

About

A lightweight, professional localization package for React and React Native applications built on top of i18next and react-i18next. This package provides a clean, type-safe API for managing translations with built-in language switching, variable injection, and component interpolation capabilities.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •