Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Feature/i18n #206

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ module.exports = {
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json'],
},
plugins: [
'import',
'tsdoc',
],
plugins: ['import', 'tsdoc'],
extends: [
'@concepta/eslint-config/nest',
'plugin:jsdoc/recommended-typescript',
Expand All @@ -21,6 +18,7 @@ module.exports = {
'**/tsconfig.json',
'**/tsconfig.eslint.json',
'**/commitlint.config.js',
'packages/i18n/src/__fixtures__/locales/**/*.json',
],
settings: {
jsdoc: {
Expand Down
294 changes: 294 additions & 0 deletions packages/i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@

# i18n Module for React

An advanced i18n (internationalization) utility for managing multiple
languages in React applications with seamless integration and modularity.

## Project

[![NPM Latest](https://img.shields.io/npm/v/@concepta/i18n)](https://www.npmjs.com/package/@concepta/i18n)
[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/i18n)](https://www.npmjs.com/package/@concepta/i18n)
[![GH Last Commit](https://img.shields.io/github/last-commit/@concepta/i18n?logo=github)](https://github.com/@concepta/i18n)
[![GH Contrib](https://img.shields.io/github/contributors/@concepta/i18n?logo=github)](https://github.com/@concepta/i18n/graphs/contributors)

# Table of Contents

1. [Tutorials](#tutorials)
- [Getting Started with i18n](#getting-started-with-i18n)
- [Installation](#installation)
- [Basic Setup](#basic-setup)
- [Example](#example)
- [Adding a Custom Backend Module](#adding-a-custom-backend-module)
- [Changing Language Dynamically](#changing-language-dynamically)
- [Adding Translations](#adding-translations)
2. [How-To Guides](#how-to-guides)
- [Creating a Custom Translation Module](#creating-a-custom-translation-module)
- [Handling Missing Translations](#handling-missing-translations)
3. [Reference](#reference)
4. [Explanation](#explanation)
- [What is i18n?](#what-is-i18n)
- [How i18n Module Works](#how-i18n-module-works)
- [Benefits of Using i18n](#benefits-of-using-i18n)
- [Common i18n Pitfalls](#common-i18n-pitfalls)

# Tutorials

## Getting Started with i18n

### Installation

Install the `@concepta/i18n` package using yarn or npm:

yarn add @concepta/i18n

npm install @concepta/i18n

## Basic Setup

To set up the i18n module, you need to initialize it with your desired
settings and add your translations. For more detailed settings documentation,
check [i18next](https://www.i18next.com/).

```ts
// i18nSetup.js
import { I18n, t } from '@concepta/i18n';

export function initI18n() {
I18n.init({
options: {
fallbackLng: 'en',
resources: {
en: {
'translation': {
hello: "Hi"
}
},
pt: {
'translation': {
hello: "Olá"
}
}
}
}
});
// initialized and ready to go!
// i18n is already initialized, because the translation resources were
// passed via init function
}

export { t };
```

```ts
import { initI18n, t } from './i18nSetup';

// Initialize i18n when the application loads
initI18n();

// result: Hi
console.log(t({ key: 'hello' }));

// result: Olá
console.log(t({
key: 'hello',
language: 'pt'
}));
```

## Example

These are basic examples to help you get started with the i18n module.

### Adding a Custom Backend Module

To add a custom backend module for fetching translations:

```json
//./locales/en/translation.json
{
"hello": "Hi"
}
```

```json
//./locales/pt/translation.json
{
"hello": "Olá"
}
```

```ts
import I18NexFsBackend, { FsBackendOptions } from 'i18next-fs-backend';
import { I18n, t } from '@concepta/i18n';

//...
I18n.init<FsBackendOptions>({
modules: [I18NexFsBackend],
options: {
initImmediate: false,
ns: ['translation'],
backend: {
loadPath: join(__dirname, './locales/{{lng}}/{{ns}}.json')
},
supportedLngs: ['en', 'pt'],
preload: ['en', 'pt'],
fallbackLng: 'en'
}
});
//...
```

# How-To Guides

## Changing Language Dynamically

You can change the language at runtime:

```ts
//...
I18n.init({
options: {
resources: {
en: {
'translation': {
hello: "Hi"
}
},
pt: {
'translation': {
hello: "Olá"
}
}
}
}
});
// result: Hi
console.log(t({ key: 'hello' }));

I18n.changeLanguage('pt');

// result: Olá
console.log(t({ key: 'hello' }));
```

## Adding Translations

To add translations dynamically, you can initialize the I18n instance first
and addtranslation resources at a later time. This approach is useful in
scenarios where translations are fetched from an external source or need to be
updated without reinitializing the entire i18n setup.

This method is particularly beneficial in the following situations:

- **Fetching Translations from an API**: When translations are retrieved from a
remote server, you can initialize i18n once and update translations as they
become available.
- **Modular Applications**: In large applications with multiple modules,
translations can be added as each module is loaded, reducing the initial load
time.
- **Real-time Updates**: If your application supports real-time updates, you
can dynamically add new translations without disrupting the user experience.

```ts
//...
I18n.init();

const locales = [
{
namespace: 'common',
language: 'en',
resource: { welcome: 'Welcome' },
},
{
namespace: 'common',
language: 'fr',
resource: { welcome: 'Bienvenue' },
},
];

I18n.addTranslations(locales);

// Welcome
console.log(t({
namespace: 'common',
key: 'welcome',
language: 'en'
}));

// Bienvenue
console.log(t({
namespace: 'common',
key: 'welcome',
language: 'fr'
}));
```

## Creating a Custom Translation Module

You can create custom translation modules to extend the i18n functionality.
Here’s how:

### **Define the Module**: Add modules on initialization

```ts
import { MyCustomModule } from './my-custom-module';
import { I18n } from '@concepta/i18n';

I18n.init({
modules: [MyCustomModule],
options: {
//...
}
});
```

## Handling Missing Translations

To handle missing translations gracefully:

```ts
import { I18n, t } from '@concepta/i18n';
I18n.translate({
key: 'missing_key',
namespace: 'common',
defaultMessage: 'This is the default text',
});

// or
t({
key: 'missing_key',
namespace: 'common',
defaultMessage: 'This is the default text',
});
```

# Reference

Please check (API Reference)[] for more information.

# Explanation

## What is i18n?

i18n, short for internationalization, refers to the process of designing
software applications that can be adapted to various languages and regions
without engineering changes.

## How i18n Module Works

The i18n module in this package provides a wrapper around i18next, allowing
seamless integration of translation modules and dynamic language switching.

### Benefits of Using i18n

- **Flexibility**: Supports multiple languages and dynamic translation updates.
- **Modularity**: Easily extendable with custom modules for translation
management.
- **Ease of Use**: Simple API to manage translations and switch languages.

### Common i18n Pitfalls

- **Missing Translations**: Ensure all necessary translations are provided for
each language.
- **Performance Overheads**: Avoid loading unnecessary translation files by
using modular imports.
21 changes: 21 additions & 0 deletions packages/i18n/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@concepta/i18n",
"version": "5.0.0",
"description": "Rockets i18n",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "BSD-3-Clause",
"publishConfig": {
"access": "public"
},
"files": [
"dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}"
],
"dependencies": {
"i18next": "^23.12.2"
},
"devDependencies": {
"i18next-fs-backend": "^2.3.1",
"i18next-http-middleware": "^3.6.0"
}
}
3 changes: 3 additions & 0 deletions packages/i18n/src/__fixtures__/locales/en/my-namespace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"hello": "Hi"
}
3 changes: 3 additions & 0 deletions packages/i18n/src/__fixtures__/locales/en/translation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"hello": "Hi from translation"
}
3 changes: 3 additions & 0 deletions packages/i18n/src/__fixtures__/locales/pt/my-namespace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"hello": "Olá"
}
3 changes: 3 additions & 0 deletions packages/i18n/src/__fixtures__/locales/pt/translation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"hello": "Olá do translation"
}
12 changes: 12 additions & 0 deletions packages/i18n/src/__fixtures__/moduleA/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { I18n } from '../../i18n';
import LOCALES from './locales';

let loaded = false;
export const initModuleATranslation = () => {
if (!loaded) {
I18n.addTranslations(LOCALES);
loaded = true;
}
};

export * from './moduleA.module.fixture';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const enUS = {
hello: 'Hi from module A',
};
export default enUS;
19 changes: 19 additions & 0 deletions packages/i18n/src/__fixtures__/moduleA/locales/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { I18nLocalesInterface } from '../../../interfaces/i18n-locales.interface';
import { ModuleAFixture } from '../moduleA.module.fixture';
import enUS from './en/ModuleAFixture';
import ptBR from './pt/ModuleAFixture';

const LOCALES: I18nLocalesInterface[] = [
{
namespace: ModuleAFixture.name,
language: 'en',
resource: enUS,
},
{
namespace: ModuleAFixture.name,
language: 'pt',
resource: ptBR,
},
];

export default LOCALES;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const ptBR = {
hello: 'Olá do modulo A',
};
export default ptBR;
Loading
Loading