pnar-i18n
v2.1.0
Published
Framework-agnostic internationalization library with support for React, Vue, and other JavaScript frameworks
Maintainers
Readme
pnar-i18n
A framework-agnostic internationalization library with first-class support for React and Vue.
Features
- ✅ Framework Agnostic - Core logic works with any JavaScript framework
- ✅ React Support - Ready-to-use React components and hooks
- ✅ Vue Support - Vue 3 composition API and plugin
- ✅ TypeScript Support - Full type safety
- ✅ Interpolation - Variable substitution in translations
- ✅ Nested Keys - Support for deep translation objects using dot notation
- ✅ Dynamic Loading - Asynchronously load translations on demand
- ✅ Language Persistence - Save language preferences to localStorage
- ✅ Lightweight - Small footprint, minimal dependencies
- ✅ Extensible - Easy to extend for additional functionality
Installation
npm install pnar-i18n
# or
yarn add pnar-i18nImport Paths
This library uses a modular approach. Import from the appropriate path based on your framework:
// React components and hooks
import { TranslationProvider, useTranslation } from 'pnar-i18n/react';
// Vue composition API and plugin
import { createI18n, useI18n } from 'pnar-i18n/vue';
// Core functionality (framework-agnostic)
import { interpolate } from 'pnar-i18n/core';Quick Start
React
import { TranslationProvider, useTranslation } from 'pnar-i18n/react';
// Define your translations
const translations = {
en: {
welcome: 'Welcome',
greeting: 'Hello {{name}}!',
nested: {
key: 'This is a nested translation'
}
},
pnar: {
welcome: 'Alæ',
greeting: 'Hey {{name}}!',
nested: {
key: 'Tha esp ippk keñan'
}
},
};
// Wrap your app with the provider
function App() {
return (
<TranslationProvider
config={{
defaultLanguage: 'en',
fallbackLanguage: 'en',
translations,
persistLanguage: true
}}
>
<MyComponent />
</TranslationProvider>
);
}
// Use translations in your components
function MyComponent() {// Vue example
import { createI18n } from 'pnar-i18n/vue';
const i18n = createI18n({
defaultLanguage: 'en',
translations: {
en: {
/* initial English translations */
},
},
loadTranslations: async (language) => {
// Fetch translations from your API
const response = await fetch(`/api/translations/${language}`);
return response.json();
},
});Using Nested Translations
// Define nested translations
const translations = {
en: {
ui: {
buttons: {
submit: 'Submit',
cancel: 'Cancel',
},
forms: {
errors: {
required: 'This field is required',
},
},
},
},
};
// Access with dot notation
t('ui.buttons.submit'); // "Submit"
t('ui.forms.errors.required'); // "This field is required"Integration with Framework Router
React Router
import { useTranslation } from 'pnar-i18n/react';
import { useParams } from 'react-router-dom';
function LocalizedPage() {
const { lang } = useParams();
const { setLanguage } = useTranslation();
// Update language based on URL param
useEffect(() => {
if (lang) {
setLanguage(lang);
}
}, [lang, setLanguage]);
// Rest of your component...
}Vue Router
<script setup>
import { useI18n } from 'pnar-i18n/vue';
import { useRoute, useRouter } from 'vue-router';
import { watch } from 'vue';
const route = useRoute();
const router = useRouter();
const { setLanguage, language } = useI18n();
// Update language based on route param
watch(
() => route.params.lang,
(newLang) => {
if (newLang) {
setLanguage(newLang);
}
},
{ immediate: true }
);
// Update route when language changes
watch(language, (newLang) => {
if (route.params.lang !== newLang) {
router.push({
params: { ...route.params, lang: newLang },
});
}
});
</script>API Reference
Core API
interpolate(text, options, config)
Interpolates variables into a translation string.
import { interpolate } from 'pnar-i18n/core';
const result = interpolate(
'Hello {{name}}!',
{ name: 'World' },
{ prefix: '{{', suffix: '}}' }
);
// "Hello World!"React API
TranslationProvider
Provider component that wraps your application and provides translation context.
<TranslationProvider config={I18nConfig}>{children}</TranslationProvider>useTranslation()
Hook to access translation functions and state within components.
const {
t, // Translation function (key, options) => string
language, // Current language code
setLanguage, // Function to change language
translations, // Complete translations object
isLoading, // Boolean indicating if translations are loading
availableLanguages, // Array of available language codes
} = useTranslation();Vue API
createI18n(config)
Creates an i18n instance for Vue applications.
const i18n = createI18n(I18nConfig);
// Use as a Vue plugin
app.use(i18n);useI18n()
Composition API hook for use within Vue components.
const {
t, // Translation function (key, options) => string
language, // Ref to current language code
setLanguage, // Function to change language
translations, // Ref to complete translations object
isLoading, // Ref to loading state
availableLanguages, // Array of available language codes
} = useI18n();TypeScript Support
The library includes complete TypeScript definitions for both React and Vue adapters:
// Common types
interface I18nConfig {
defaultLanguage: string;
fallbackLanguage?: string;
translations?: Translations;
loadTranslations?: (language: string) => Promise<TranslationMap>;
persistLanguage?: boolean;
storageKey?: string;
interpolation?: {
prefix: string;
suffix: string;
};
}
interface TranslationOptions {
fallback?: string;
[key: string]: any; // For interpolation variables
}
// Define strongly typed translations
interface CustomTranslations {
en: {
greeting: string;
welcome: string;
// etc...
};
pnar: {
greeting: string;
welcome: string;
// etc...
};
}
// Use with React
const { t } = useTranslation<CustomTranslations>();
// Use with Vue
const { t } = useI18n<CustomTranslations>();return (
<button onClick={() => setLanguage(language === 'en' ? 'pnar' : 'en')}>
Switch Language
</button>
</div>); }
### Vue
```typescript
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { createI18n } from 'pnar-i18n/vue';
// Define your translations
const translations = {
en: {
welcome: 'Welcome',
greeting: 'Hello {{name}}!',
nested: {
key: 'This is a nested translation'
}
},
pnar: {
welcome: 'Alæ',
greeting: 'Hey {{name}}!',
nested: {
key: 'Tha esp ippk keñan'
}
},
};
// Create and configure i18n instance
const i18n = createI18n({
defaultLanguage: 'en',
fallbackLanguage: 'en',
translations,
persistLanguage: true
});
// Create and mount your app with i18n
const app = createApp(App);
app.use(i18n);
app.mount('#app');<!-- Component.vue -->
<script setup>
import { useI18n } from 'pnar-i18n/vue';
import { computed } from 'vue';
const { t, language, setLanguage } = useI18n();
const toggleLanguage = () => {
setLanguage(language.value === 'en' ? 'pnar' : 'en');
};
</script>
<template>
<div>
<h1>{{ t('welcome') }}</h1>
<p>{{ t('greeting', { name: 'John' }) }}</p>
<p>{{ t('nested.key') }}</p>
<button @click="toggleLanguage">Switch Language</button>
</div>
</template>Configuration Options
Both React and Vue adapters accept the same configuration options:
| Option | Type | Default | Description |
| ------------------ | ---------- | -------------------------------- | --------------------------------------------------- |
| defaultLanguage | string | 'en' | The default language to use |
| fallbackLanguage | string | 'en' | Language to use when a translation is not found |
| translations | object | {} | Object containing all translations |
| loadTranslations | function | — | Async function to load translations on demand |
| persistLanguage | boolean | true | Whether to save language preference to localStorage |
| storageKey | string | 'pnar-i18n-language' | Key to use for localStorage |
| interpolation | object | { prefix: '{{', suffix: '}}' } | Configure interpolation markers |
Advanced Usage
Dynamic Translation Loading
// React example
<TranslationProvider
config={{
defaultLanguage: 'en',
translations: {
en: {
/* initial English translations */
},
},
loadTranslations: async (language) => {
// Fetch translations from your API
const response = await fetch(`/api/translations/${language}`);
return response.json();
},
}}
>
{/* Your app */}
</TranslationProvider>setLanguage, // Change language isLoading, // Loading state availableLanguages, // Available languages } = useTranslation();
### usePluralize Hook
```typescript
const pluralize = usePluralize();
// Basic usage
pluralize('item', 1); // "item"
pluralize('item', 5); // "items" (if plural form exists)
// With custom translations
pluralize('apple', 3); // Uses translation key 'apple_many' if availableuseDateLocalization Hook
const localizeDate = useDateLocalization();
// Format dates according to current language
localizeDate(new Date(), 'long'); // "December 25, 2023" (localized)
localizeDate(new Date(), 'short'); // "12/25/23" (localized)Advanced Usage
Dynamic Translation Loading
const loadTranslations = async (language: string) => {
const response = await fetch(`/locales/${language}.json`);
return response.json();
};
<TranslationProvider
config={{
defaultLanguage: 'en',
loadTranslations,
}}
>
<App />
</TranslationProvider>;Pluralization
import { usePluralize } from 'pnar-i18n/react';
function MyComponent() {
const pluralize = usePluralize();
return <p>{pluralize('item', count)}</p>;
}Language Switching
You can create your own language switcher component using the provided language switching functionality:
import { useTranslation } from 'pnar-i18n/react';
function LanguageSwitcher() {
const { currentLanguage, setLanguage, availableLanguages } = useTranslation();
return (
<div>
{availableLanguages.map((lang) => (
<button
key={lang}
onClick={() => setLanguage(lang)}
style={{ fontWeight: currentLanguage === lang ? 'bold' : 'normal' }}
>
{lang.toUpperCase()}
</button>
))}
</div>
);
}For Vue applications:
<script setup>
import { useI18n } from 'pnar-i18n/vue';
const { currentLanguage, setLanguage, availableLanguages } = useI18n();
</script>
<template>
<div>
<button
v-for="lang in availableLanguages"
:key="lang"
@click="setLanguage(lang)"
:style="{ fontWeight: currentLanguage === lang ? 'bold' : 'normal' }"
>
{{ lang.toUpperCase() }}
</button>
</div>
</template>Configuration Options
| Option | Type | Default | Description |
| ------------------ | -------------- | ------------------------------ | ------------------------ |
| defaultLanguage | string | 'en' | Default language |
| fallbackLanguage | string | 'en' | Fallback language |
| translations | Translations | {} | Static translations |
| loadTranslations | Function | - | Dynamic loading function |
| persistLanguage | boolean | true | Save language preference |
| storageKey | string | 'pnar-i18n-language' | localStorage key |
| interpolation | object | {prefix: '{{', suffix: '}}'} | Interpolation markers |
Examples
Basic Usage with JSON Files
// translations/en.json
{
"welcome": "Welcome",
"greeting": "Hello {{name}}!",
"items": "item",
"items_many": "items"
}
// translations/pnar.json
{
"welcome": "Alæ",
"greeting": "Hey[Hei/Hoi/Hiw] {{name}}!",
"items": "ki item",
"items_many": "Bõn item"
}
// App.tsx
import React from 'react';
import { TranslationProvider, useTranslation, usePluralize } from 'pnar-i18n/react';
const loadTranslations = async (language: string) => {
const response = await fetch(`/translations/${language}.json`);
return response.json();
};
function App() {
return (
<TranslationProvider
config={{
defaultLanguage: 'en',
loadTranslations
}}
>
<Header />
<Content />
</TranslationProvider>
);
}
function Header() {
const { currentLanguage, setLanguage, availableLanguages } = useTranslation();
return (
<header>
<div>
{availableLanguages.map(lang => (
<button
key={lang}
onClick={() => setLanguage(lang)}
style={{ fontWeight: currentLanguage === lang ? 'bold' : 'normal' }}
>
{lang.toUpperCase()}
</button>
))}
</div>
</header>
);
}
function Content() {
const { t } = useTranslation();
const pluralize = usePluralize();
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: 'John' })}</p>
<p>{pluralize('items', 5)}</p>
</div>
);
}Development
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode
npm run dev
# Run tests
npm run test
# Run linting
npm run lint
# Type checking
npm run type-checkContributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT /Users/armegochylla/Projects/pnar-world-admin-vite-react-ts/pnar-i18n/README.md
