From 6eb14a4964211bf84798c44718a42b345730f412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Marie=20De=20Mey?= Date: Wed, 1 May 2024 02:51:59 +0300 Subject: [PATCH] mainly docs --- README.md | 6 +- docs/README.md | 10 ++-- docs/_config.yml | 1 - docs/client.md | 11 +++- docs/db.md | 71 +++++++++++++++++++++++ docs/interpolation.md | 1 + docs/server.md | 1 + docs/translator.md | 28 ++++++---- package-lock.json | 115 +++++++++++++++++++++++++++++++++++++- package.json | 3 +- rollup.config.js | 2 + src/client/helpers.ts | 2 +- src/server/interactive.ts | 3 - src/types.d.ts | 4 +- 14 files changed, 228 insertions(+), 30 deletions(-) delete mode 100644 docs/_config.yml create mode 100644 docs/db.md create mode 100644 docs/interpolation.md create mode 100644 docs/server.md diff --git a/README.md b/README.md index 1c85e34..dc07fe0 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/docs/README.md b/docs/README.md index da7b90a..c2f5f57 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index bad8266..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-midnight diff --git a/docs/client.md b/docs/client.md index 183fb98..3ac67e0 100644 --- a/docs/client.md +++ b/docs/client.md @@ -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. @@ -74,6 +80,7 @@ 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 @@ -81,6 +88,6 @@ export interface ReportingClient extends OmnI18nClient { } ``` -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` \ No newline at end of file +> Of course, there will be no `super` diff --git a/docs/db.md b/docs/db.md new file mode 100644 index 0000000..05e5432 --- /dev/null +++ b/docs/db.md @@ -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 + +interface DB { + list(locales: Locale[], zone: Zone): Promise +} +``` + +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 = { + text: string + infos: TextInfos +} +type WorkDictionaryEntry = { + locales: { [locale: Locale]: WorkDictionaryText } + zone: Zone + infos: KeyInfos +} +type WorkDictionary = Record +workList(locales: Locale[]): Promise +``` + +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> +isSpecified(key: string, locales: Locale[]): Promise +``` diff --git a/docs/interpolation.md b/docs/interpolation.md new file mode 100644 index 0000000..4640904 --- /dev/null +++ b/docs/interpolation.md @@ -0,0 +1 @@ +# TODO diff --git a/docs/server.md b/docs/server.md new file mode 100644 index 0000000..4640904 --- /dev/null +++ b/docs/server.md @@ -0,0 +1 @@ +# TODO diff --git a/docs/translator.md b/docs/translator.md index f71e330..6af5835 100644 --- a/docs/translator.md +++ b/docs/translator.md @@ -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 @@ -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) diff --git a/package-lock.json b/package-lock.json index 41c76f9..d3ee95d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omni18n", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omni18n", - "version": "1.0.3", + "version": "1.0.4", "license": "ISC", "dependencies": { "hjson": "^3.2.2" @@ -15,6 +15,7 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", "@types/hjson": "^2.4.6", "@types/jest": "^29.5.12", "eslint": "^9.1.1", @@ -1483,6 +1484,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -1656,6 +1667,28 @@ } } }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", @@ -2505,6 +2538,12 @@ "node": ">=4" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/common-sequence": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.2.tgz", @@ -5077,6 +5116,15 @@ } ] }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -5347,6 +5395,26 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -5362,6 +5430,15 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5404,6 +5481,12 @@ "node": ">=8" } }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true + }, "node_modules/sort-array": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.5.tgz", @@ -5626,6 +5709,34 @@ "integrity": "sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==", "dev": true }, + "node_modules/terser": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", diff --git a/package.json b/package.json index 7175dba..ab537b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omni18n", - "version": "1.0.4", + "version": "1.0.5", "description": "", "main": "dist/index.js", "module": "dist/index.js", @@ -31,6 +31,7 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", "@types/hjson": "^2.4.6", "@types/jest": "^29.5.12", "eslint": "^9.1.1", diff --git a/rollup.config.js b/rollup.config.js index 86fd04f..b2f844f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,6 +2,7 @@ import resolve from '@rollup/plugin-node-resolve' import commonjs from '@rollup/plugin-commonjs' import typescript from 'rollup-plugin-typescript2' import json from '@rollup/plugin-json' +import terser from '@rollup/plugin-terser' export default { input: './src/index.ts', @@ -19,6 +20,7 @@ export default { exclude: ['./node_modules'] } }), + terser(), json() ] } diff --git a/src/client/helpers.ts b/src/client/helpers.ts index 3329879..37735a6 100644 --- a/src/client/helpers.ts +++ b/src/client/helpers.ts @@ -97,7 +97,7 @@ export function translator(context: TContext): Translator { return target case 'constructor': return String - case 'then': // Must be unthenable in order to be awaited + case 'then': // Must be un-then-able in order to be awaited return new Proxy( {}, { diff --git a/src/server/interactive.ts b/src/server/interactive.ts index 88a0db1..ebf1c8c 100644 --- a/src/server/interactive.ts +++ b/src/server/interactive.ts @@ -29,9 +29,6 @@ export default class InteractiveServer< workList(locales: OmnI18n.Locale[]): Promise { return this.db.workList(locales) } - isSpecified(key: string, locales: OmnI18n.Locale[]): Promise { - return this.db.isSpecified(key, locales) - } async save() { const servers = new Set() diff --git a/src/types.d.ts b/src/types.d.ts index 129502b..f534f82 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -15,13 +15,13 @@ declare namespace OmnI18n { * Dictionary used between the server and the DB * key => [locale, text] */ - type RawDictionary = Record + type RawDictionary = Record type WorkDictionaryText = { text: string infos: TextInfos } type WorkDictionaryEntry = { - locales: { [locale: OmnI18n.Locale]: WorkDictionaryText } + locales: { [locale: Locale]: WorkDictionaryText } zone: Zone infos: KeyInfos }