Skip to content

Commit

Permalink
mainly docs
Browse files Browse the repository at this point in the history
  • Loading branch information
eddow committed Apr 30, 2024
1 parent d746543 commit 6eb14a4
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 30 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Generic i18n library managing the fullstack interaction in a CI/CD pace. The fac

It can even manage update of all (concerned) clients when a translation is modified

The main documentation on [GitHub pages](https://emedware.github.io/omni18n/) or in [the repository](./docs/README.md)
The main documentation in [the repository](./docs/README.md)

## General structure

Expand Down Expand Up @@ -83,9 +83,9 @@ Example:
}
```

In this case, _both_ `T.fld.name` _and_ `T.fld.name.short` will retrieve `"Name"`, so that, if the project use shortened notations, it can display `T.fld[field].short` without demanding all the fields to have a `short` version in all languages
In this case, _both_ `T.fld.name` _and_ `T.fld.name.short` will retrieve `"Name"` so that, if the project use shortened notations, it can display `T.fld[field].short` without demanding all the fields to have a `short` version in all languages

Rule of the thumb: No value should be given as root keys. Every meaningful text has a category and should therefore be a sub-key
Rule of the thumb: No value should be given as root keys. Every meaningful text has a category and should therefore be a sub-key. Also, some helpers function detect if there is a dot to identify keys vs. other kind of designations.

### Locales

Expand Down
10 changes: 5 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# OmnI18n

[Overview](https://github.com/emedware/omni18n/blob/main/README.md)
The first document presents an [overview](../README.md), here is a more detailed description

> :warning: **Work in progress!**
> :warning: **Work in progress!**. Remaining: server, DB, interpolation
Projects using OmnI18n use it in 4 layers

1. [The `client`](./client.md): The client manages the cache and download along with providing [`Translator`s](./translator.md)
1. [The `client`](./client.md): The client manages the cache and download along with providing [`Translator`s](./translator.md) that will [interpolate](./interpolation.md)
2. (optional) The HTTP or any other layer. This part is implemented by the user
3. The `server`: The server exposes functions to interact with the languages
4. The `database`: A class implementing some interface that interacts directly with a database
3. [The `server`](./server.md): The server exposes functions to interact with the languages
4. [The `database`](./db.md): A class implementing some interface that interacts directly with a database
1 change: 0 additions & 1 deletion docs/_config.yml

This file was deleted.

11 changes: 9 additions & 2 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ I18nClient(locales: OmnI18n.Locale[], condense: OmnI18n.Condense, onModification
const client = new I18nClient(['fr', 'en'], server.condense, frontend.refreshTexts)
```

#### Locales

Locales default to the more generic ones. Here, we can give several for fall-back purpose. In the case `['fr', 'en']`, if a french translation is not found while an english one is, the english version will be displayed while triggering the [`missing` report](./client.md#reports)

If the locales `['fr-CA', 'en-UK', 'de-DE']` are given, the actual list of locales that will be used is `['fr-CA', 'fr', '', 'en-UK', 'en', 'de-DE', 'de']`. The `missing` report will be called when the used locale is english or german.

### Reports

There are two ways to manage reports. There are also two report types : missing and error. The first one is for when a key is missing, the second one only happens when interpolating.
Expand Down Expand Up @@ -74,13 +80,14 @@ reports.error = ({ key, client }: TContext, error: string, spec: object) => {
#### OO reporting

The interface `ReportingClient` exposes the methods :

```ts
export interface ReportingClient extends OmnI18nClient {
missing(key: string, fallback: string | undefined, zones: OmnI18n.Zone[]): string
error(key: string, error: string, spec: object, zones: OmnI18n.Zone[]): string
}
```

Applications implementing this interface will have it called instead of the global `reports` value.
Clients implementing this interface will have it called instead of the global `reports` value.

> Of course, there will be no `super`
> Of course, there will be no `super`
71 changes: 71 additions & 0 deletions docs/db.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Database interface

## Structure

The main structure is `key -> translations`. In SQL, it would be translated as 2 tables:

- One for the key, the text-key - like "msg.greet" - being the primary key
- One for the translations, where the primary key is the couple (text-key, locale)

In mongo or json-oriented DB, a key object could directly give a list of translations `locale -> text`

The database interface is the one provided to the server. The first one is fairly simple:

```ts
type RawDictionary = Record<string, [Locale, string]>

interface DB {
list(locales: Locale[], zone: Zone): Promise<RawDictionary>
}
```

That `list` function is a glorified `SELECT` who gives all keys given in a zone and, for them, the [_first locale from the given list_](./client.md#locales) that has a translation - and the translation of course

### Role in OmnI18n ecosystem

The DB role is purely to deal with a database. The [`server`](./server.md) will often mimic functions and their signature (`modify`, `reKey`, ...) and while the `server` role is to propagate information to both the client and the DB, a DB's role is purely the one of an adapter toward a database.

## InteractiveDB

A kind of API has been designed for the server to be able to _modify_ the content of the DB : `InteractiveDB`.

### Infos

Here, we get already in the realm where we can specify `KeyInfos` and `TextInfos`. The former is given by developers, in english or some common language if text is needed - and appear in the `keys` database - and the `TextInfo`, more often used/edited by the translators and appearing in the `translations` database.

These are generic arguments - It means, if one implements a DB adapter, care should be taken to store/retrieve them - even if we don't know what structure they have. They all :

```ts
...Infos extends {} = {}
```

### Specific getters

#### Work list

```ts
type WorkDictionaryText<TextInfos> = {
text: string
infos: TextInfos
}
type WorkDictionaryEntry<KeyInfos, TextInfos> = {
locales: { [locale: Locale]: WorkDictionaryText<TextInfos> }
zone: Zone
infos: KeyInfos
}
type WorkDictionary = Record<string, WorkDictionaryEntry>
workList(locales: Locale[]): Promise<WorkDictionary>
```
Given a list of locales, find all their translations
> No `zone` fuss, and it's not "the first translation", it's all of them.
This function is indeed used to populate translator's list for working on it ... working list.
#### Get a single key, check whether it is specified
```ts
get(key: string): Promise<Record<Locale, string>>
isSpecified(key: string, locales: Locale[]): Promise<undefined | {} | TextInfos>
```
1 change: 1 addition & 0 deletions docs/interpolation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TODO
1 change: 1 addition & 0 deletions docs/server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TODO
28 changes: 18 additions & 10 deletions docs/translator.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@
A translator represent a `TContext`, meaning an [`I18nClient`](./client.md), zones and a text-key (so, potentially a translated text)

A translator `blup` can:

- `blup.subkey` or `blup['sub.key']`: Retrieve another translator for a sub-key
- `blup(1, 2, 3)`: Interpolate, optionally with arguments the translated key
- `"Here: " + blup`: Be interpreted as a string, in which case it will interpolate without arguments
-
> :warning: :hotsprings: `blup.then` returns an object that throws errors when accessed!
> If `blup.then` would return a translator, therefore a function, it would look like "thenable" and therefore make bugs when returned from a promise
- > :warning: :hotsprings: `blup.then` returns an object that throws errors when accessed!
> If `blup.then` would return a translator, therefore a function, it would look like "thenable" and therefore make bugs when returned from a promise
## Life of a translator

> Shall no one take it personally
A main translator is created with
A main translator is created with

```js
const T = await client.enter('zone1', 'zone2')
```

>The `await` part come from the fact the client might have to download some parts of the dictionary. It happens few, but it happens at least each time a page is rendered/a server is launched.
> The `await` part come from the fact the client might have to download some parts of the dictionary. It happens few, but it happens at least each time a page is rendered/a server is launched.
This is a "root translator" and, therefore, its call takes a key as first argument: `T('my.key', ...)`, but is a regular translator in all other regards

Expand Down Expand Up @@ -51,24 +52,31 @@ A `Translatable` is just an object whose leafs are string. The function `bulkObj

```ts
const T = await client.enter('myLib')
myLibrary.messages = bulkObject(T, {
tooLong: 'err.thatLib.tooLong',
empty: 'err.thatLib.empty',
play: 'msg.thatLib.play',
}, ...args);
myLibrary.messages = bulkObject(
T,
{
tooLong: 'err.thatLib.tooLong',
empty: 'err.thatLib.empty',
play: 'msg.thatLib.play'
},
...args
)
```

### Bulk from the dictionary

Another way let that library' structure be described directly in the dictionary

> Don't forget that translators can read keys and write texts, only developers edit the keys
Imagining the dictionary contains these keys:

- `groups.thatLib.tooLong`
- `groups.thatLib.empty`
- `groups.thatLib.play`

We can now call

```ts
const T = await client.enter('myLib')
myLibrary.messages = bulkDictionary(T.groups.thatLib, ...args)
Expand Down
Loading

0 comments on commit 6eb14a4

Please sign in to comment.