Skip to content

Commit

Permalink
Fallback, optimisations, types, vite/sveltekit demo
Browse files Browse the repository at this point in the history
  • Loading branch information
eartharoid committed Apr 17, 2024
1 parent 624c01e commit 299528b
Show file tree
Hide file tree
Showing 28 changed files with 979 additions and 394 deletions.
2 changes: 1 addition & 1 deletion packages/cif/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ParsedMessages } from '@eartharoid/i18n';
import type { ParsedMessages } from '@eartharoid/i18n/types/types';

export function ctom(cif: string): ParsedMessages;

Expand Down
2 changes: 1 addition & 1 deletion packages/cif/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eartharoid/cif",
"version": "1.0.0",
"version": "1.0.0-alpha.1",
"description": "Convert to and from an efficient i18n message file format",
"main": "dist/index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/cif/src/ctom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {
ParsedMessage,
ParsedMessages
} from '@eartharoid/i18n';
} from '@eartharoid/i18n/types/types.js';
import control from './control.js';

export default function ctom(cif: string): ParsedMessages {
Expand Down
2 changes: 1 addition & 1 deletion packages/cif/src/mtoc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ExtractedMessageObject, ParsedMessages } from '@eartharoid/i18n';
import type { ExtractedMessageObject, ParsedMessages } from '@eartharoid/i18n/types/types.js';
import control from './control.js';

export default function mtoc(messages: ParsedMessages): string {
Expand Down
Binary file modified packages/cif/test/test.cif
Binary file not shown.
46 changes: 44 additions & 2 deletions packages/cif/test/test.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
}
],
[
"ns:plural.age.zero",
"ns:plural.age.=0",
{
"t": "You were born recently (you'll never see this message)"
"t": "You were born recently"
}
],
[
Expand Down Expand Up @@ -185,6 +185,34 @@
"t": "Hello"
}
],
[
"ns:time",
{
"t": "The time is ",
"p": [
[
12,
{
"v": "time"
}
]
]
}
],
[
"ns:list",
{
"t": "Did you travel by ?",
"p": [
[
18,
{
"v": "list"
}
]
]
}
],
[
"ns:repeated",
{
Expand All @@ -211,6 +239,20 @@
]
}
],
[
"ns:relative_past",
{
"t": "I ate chocolate ",
"p": [
[
16,
{
"v": "relative"
}
]
]
}
],
[
"ns:placeholder_getters.classrooms",
{
Expand Down
171 changes: 1 addition & 170 deletions packages/i18n/README.md
Original file line number Diff line number Diff line change
@@ -1,172 +1,3 @@
# i18n

*Simple, small, and fast localisation.*

## Installation

- `pnpm add @eartharoid/i18n` or
- `yarn add @eartharoid/i18n` or
- `npm i @eartharoid/i18n`

## Usage

```js
const I18n = require('@eartharoid/i18n');
const i18n = new I18n('english', {
english: {
example: 'Hello, world'
},
russian: {
example: 'https://www.youtube.com/watch?v=bwnksI2ZoJI'
}
});

// note: you should check if the locale exists in i18n.locales first
const messages = i18n.getLocale('russian'); // get the locale
console.log(messages('example')); // -> https://www.youtube.com/watch?v=bwnksI2ZoJI

// this code does exactly the same
console.log(i18n.getMessage('russian', 'example'));
```

### Placeholders

i18n supports both positional and named placeholders.

```js
{ // a locale object
"positional": {
"strings": "I like %s",
"numbers": "%d %s %d = %d"
},
"named": {
"example1": "Hi, I'm {name} and I am from {location}",
"example2": "Hi, I'm {person.name} and I am from {person.location}"
}
}
```

> Also note that messages and named placeholders can be nested
```js
messages('positional.strings', 'chocolate'); // I like chocolate

messages('positional.numbers', 5, '+', 5, 10); // 5 + 5 = 10

messages('named.example1', {
name: 'Someone',
location: 'Somewhere'
}); // Hi, I'm Someone and I am from Somewhere

messages('named.example2', {
person: {
name: 'Someone',
location: 'Somewhere'
}
}); // Hi, I'm Someone and I am from Somewhere
```

### Pluralisation

i18n supports basic pluralisation. If the message is an array, **the first placeholder value will be "eaten" (consumed)** and the correct message will be returned.

```js
[
"1",
"anything else"
]
```

or

```js
[
"0",
"1",
"anything else"
]
```

```js
{ // a locale object
"example1": [
"You only have one %s",
"You have %d %ss"
],
"example2": [
"You don't have any {item}s",
"You only have one {item}",
"You have {number} {item}s"
]
}
```

```js
messages('example1', 1, 1, 'item')
messages('example2', 0, {
number: 0,
item: 'car'
})
```

### API

#### `new I18n(default_locale, locales)`

> Create a new I18n instance
- `default_locale` - the name of the default locale
- `locales` - an object of localised messages

#### `I18n#default_locale`

> The name of the default locale
#### `I18n#locales`

> An array of the names of the locales you created
#### `I18n#getAllMessages(message, ...args)`

> Get a message in all locales
- `message` - dot notation string for the message
- `...args` - placeholders/pluralisation

#### `I18n#getLocale(locale)`

> Get a locale
- `locale` - locale name

Returns a function which calls [`I18n#getMessage`](#i18ngetmessagelocale-message-args) using the given locale name (or the default).

#### `I18n#getMessage(locale, message, ...args)`

> Get a message from a specific locale
- `locale` - locale name
- `message` - dot notation string for the message
- `...args` - placeholders/pluralisation

#### `I18n#getMessages(locales, message, ...args)`

> Get a message from multiple locales
- `locales` - array of locale names
- `message` - dot notation string for the message
- `...args` - placeholders/pluralisation

## Support

[![Discord support server](https://discordapp.com/api/guilds/451745464480432129/widget.png?style=banner4)](https://lnk.earth/discord)

## Donate

[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/eartharoid)

## License

[MIT license](https://github.com/eartharoid/i18n/blob/master/LICENSE).

© 2022 Isaac Saunders
Super small and incredibly fast localisation *with no documentation*.
2 changes: 1 addition & 1 deletion packages/i18n/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eartharoid/i18n",
"version": "2.0.0",
"version": "2.0.0-alpha.1",
"description": "Simple and lightweight message localisation",
"main": "dist/index.js",
"type": "module",
Expand Down
15 changes: 6 additions & 9 deletions packages/i18n/src/I18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,14 @@ export default class I18n extends I18nLite {

/**
* Resolve missing translations
* @param {string} default_locale_id
* @param {Record<string, string[]>} [fallback_map]
* @returns {Fallen}
*/
public fallback(default_locale_id: string, fallback_map?: Record<string, string[]>): Fallen {
public fallback(fallback_map?: Record<string, string[]>): Fallen {
if (!this.default_locale_id) throw new Error('No default locale is set');
let ordered_ids: string[];
const default_locale = this.locales.get(default_locale_id);
const default_locale = this.locales.get(this.default_locale_id);
const locale_ids = Array.from(this.locales.keys());
// const fallen: Fallen = locale_ids.reduce((obj, id) => (obj[id] = [], obj), {});
const fallen: Fallen = {};

if (fallback_map) {
Expand All @@ -90,14 +89,12 @@ export default class I18n extends I18nLite {
if (fallback_map) {
fallback_order = [
...(fallback_map[locale_id] || []),
default_locale_id
this.default_locale_id
];
} else {
// const idx = locale_id.indexOf('-');
// const base_language = idx === -1 ? locale_id : locale_id.substring(0, idx); // locale_id.split('-')[0]
const base_language = new Intl.Locale(locale_id).language;
if (base_language !== locale_id && this.locales.has(base_language)) fallback_order = [base_language, default_locale_id];
else fallback_order = [default_locale_id];
if (base_language !== locale_id && this.locales.has(base_language)) fallback_order = [base_language, this.default_locale_id];
else fallback_order = [this.default_locale_id];
}
const locale = this.locales.get(locale_id);
for (const [key] of default_locale) {
Expand Down
3 changes: 2 additions & 1 deletion packages/i18n/test/with-fallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ fs.readdirSync('test/locales')
});

const i18n = new I18n({
default_locale_id: 'en',
defer_extraction: false,
});
for (const [k, v] of Object.entries(locales)) i18n.load(k, v);
i18n.fallback('en');
i18n.fallback();

test('not missing', t => {
const expected = 'Dette er så enkelt som det blir';
Expand Down
2 changes: 1 addition & 1 deletion packages/i18n/types/I18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default class I18n extends I18nLite {
placeholder_regex: RegExp;
constructor(options?: Partial<I18nOptions>);
extract(message: string): ExtractedMessageObject;
fallback(default_locale_id: string, fallback_map?: Record<string, string[]>): Fallen;
fallback(fallback_map?: Record<string, string[]>): Fallen;
load(locale_id: string, messages: RawMessages, namespace?: string): Locale;
parse(messages: RawMessages, namespace?: string): ParsedMessages;
}
4 changes: 2 additions & 2 deletions packages/vite-plugin-i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default {

## Usage

```ts
<!-- ```ts
// import the extended class **from the plugin**
import { I18n } from '@eartharoid/vite-plugin-i18n';
Expand All @@ -98,4 +98,4 @@ const i18n = new I18n();
const t = i18n.load(await import(`./locales/${locale_id}.json`)).createTranslator();
// ...
t('message.key');
```
``` -->
23 changes: 14 additions & 9 deletions packages/vite-plugin-i18n/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type { Locale, I18nLiteOptions } from '@eartharoid/i18n/index.d.ts';
import type { Plugin } from 'vite';
import type { ParsedMessages } from '@eartharoid/i18n/types/types';

export interface CIFModule {
cif?: string,
json?: ParsedMessages,
cif: string,
locale_id: string,
}

export interface JSONModule {
json: ParsedMessages,
locale_id: string,
}

Expand All @@ -14,15 +19,15 @@ export interface I18nPluginOptions {
parser?(src: string): string,
}

export interface I18nPlugin {
export interface I18nVitePlugin {
enforce: string,
name: string,
transform(code: string, id: string): unknown,
}

export class I18n {
constructor(options: Partial<I18nLiteOptions>);
public load(module: CIFModule): Locale
}

export function I18nPlugin(options: I18nPluginOptions): I18nPlugin;
export function importCIF(...modules: CIFModule[]): [string, ParsedMessages]

export function importJSON(...modules: JSONModule[]): [string, ParsedMessages]

export function I18nPlugin(options: I18nPluginOptions): Plugin<I18nVitePlugin>;
Loading

0 comments on commit 299528b

Please sign in to comment.