smartloc 1.2.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
  2. package/.vscode/launch.json +65 -0
  3. package/.vscode/settings.json +14 -0
  4. package/.vscode/tasks.json +27 -0
  5. package/README.md +225 -0
  6. package/build/README.md +225 -0
  7. package/build/package.json +60 -0
  8. package/bun.lock +310 -0
  9. package/index.js +1 -26
  10. package/package.json +17 -18
  11. package/samples/express/main.ts +37 -0
  12. package/samples/express/package.json +10 -0
  13. package/samples/graphql/main.ts +73 -0
  14. package/samples/graphql/package.json +15 -0
  15. package/src/adapters/adapter-base.ts +58 -0
  16. package/src/adapters/index.ts +31 -0
  17. package/src/adapters/json.ts +18 -0
  18. package/src/adapters/xliff.ts +30 -0
  19. package/src/cli/cli.ts +137 -0
  20. package/src/cli/collect.ts +196 -0
  21. package/src/cli/interfaces.ts +7 -0
  22. package/src/cli/reconciliate.ts +119 -0
  23. package/src/cli/utils.ts +36 -0
  24. package/src/core/default-locale.tsx +54 -0
  25. package/src/core/interfaces.ts +49 -0
  26. package/{core/json-utils.js → src/core/json-utils.ts} +45 -34
  27. package/src/core/literal.ts +66 -0
  28. package/src/core/load.ts +82 -0
  29. package/src/core/locale-list.ts +128 -0
  30. package/src/core/locale.tsx +75 -0
  31. package/src/core/smartloc.tsx +338 -0
  32. package/src/core/tag.ts +40 -0
  33. package/src/core/utils.tsx +24 -0
  34. package/src/express.ts +93 -0
  35. package/{graphql.js → src/graphql.ts} +80 -68
  36. package/src/index.ts +7 -0
  37. package/src/node/index.ts +1 -0
  38. package/src/node/load.ts +32 -0
  39. package/test/collect.spec.ts +45 -0
  40. package/test/reconciliation.spec.ts +132 -0
  41. package/test/serialization.spec.ts +162 -0
  42. package/test/translation.spec.ts +61 -0
  43. package/tsconfig.json +37 -0
  44. package/tslint.json +103 -0
  45. package/adapters/adapter-base.d.ts +0 -10
  46. package/adapters/adapter-base.js +0 -46
  47. package/adapters/adapter-base.js.map +0 -1
  48. package/adapters/index.d.ts +0 -3
  49. package/adapters/index.js +0 -33
  50. package/adapters/index.js.map +0 -1
  51. package/adapters/json.d.ts +0 -6
  52. package/adapters/json.js +0 -22
  53. package/adapters/json.js.map +0 -1
  54. package/adapters/xliff.d.ts +0 -6
  55. package/adapters/xliff.js +0 -32
  56. package/adapters/xliff.js.map +0 -1
  57. package/cli/cli.d.ts +0 -2
  58. package/cli/cli.js +0 -128
  59. package/cli/cli.js.map +0 -1
  60. package/cli/collect.d.ts +0 -12
  61. package/cli/collect.js +0 -151
  62. package/cli/collect.js.map +0 -1
  63. package/cli/interfaces.d.ts +0 -25
  64. package/cli/interfaces.js +0 -3
  65. package/cli/interfaces.js.map +0 -1
  66. package/cli/reconciliate.d.ts +0 -6
  67. package/cli/reconciliate.js +0 -108
  68. package/cli/reconciliate.js.map +0 -1
  69. package/cli/utils.d.ts +0 -7
  70. package/cli/utils.js +0 -54
  71. package/cli/utils.js.map +0 -1
  72. package/core/base-loc.d.ts +0 -3
  73. package/core/base-loc.js +0 -20
  74. package/core/base-loc.js.map +0 -1
  75. package/core/default-locale.d.ts +0 -8
  76. package/core/default-locale.js +0 -42
  77. package/core/default-locale.js.map +0 -1
  78. package/core/interfaces.d.ts +0 -32
  79. package/core/interfaces.js +0 -3
  80. package/core/interfaces.js.map +0 -1
  81. package/core/json-utils.d.ts +0 -19
  82. package/core/json-utils.js.map +0 -1
  83. package/core/literal.d.ts +0 -9
  84. package/core/literal.js +0 -52
  85. package/core/literal.js.map +0 -1
  86. package/core/locale-list.d.ts +0 -19
  87. package/core/locale-list.js +0 -84
  88. package/core/locale-list.js.map +0 -1
  89. package/core/locale.d.ts +0 -13
  90. package/core/locale.js +0 -34
  91. package/core/locale.js.map +0 -1
  92. package/core/locstr-array.d.ts +0 -10
  93. package/core/locstr-array.js +0 -48
  94. package/core/locstr-array.js.map +0 -1
  95. package/core/smartloc.d.ts +0 -64
  96. package/core/smartloc.js +0 -222
  97. package/core/smartloc.js.map +0 -1
  98. package/core/tag.d.ts +0 -5
  99. package/core/tag.js +0 -17
  100. package/core/tag.js.map +0 -1
  101. package/core/utils.d.ts +0 -1
  102. package/core/utils.js +0 -13
  103. package/core/utils.js.map +0 -1
  104. package/express.d.ts +0 -12
  105. package/express.js +0 -85
  106. package/express.js.map +0 -1
  107. package/graphql.d.ts +0 -12
  108. package/graphql.js.map +0 -1
  109. package/index.d.ts +0 -7
  110. package/index.js.map +0 -1
  111. package/node/index.d.ts +0 -1
  112. package/node/index.js +0 -14
  113. package/node/index.js.map +0 -1
  114. package/node/load.d.ts +0 -12
  115. package/node/load.js +0 -44
  116. package/node/load.js.map +0 -1
@@ -0,0 +1,111 @@
1
+ ---
2
+ description: Use Bun instead of Node.js, npm, pnpm, or vite.
3
+ globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ Default to using Bun instead of Node.js.
8
+
9
+ - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
10
+ - Use `bun test` instead of `jest` or `vitest`
11
+ - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
12
+ - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
13
+ - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
14
+ - Bun automatically loads .env, so don't use dotenv.
15
+
16
+ ## APIs
17
+
18
+ - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
19
+ - `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
20
+ - `Bun.redis` for Redis. Don't use `ioredis`.
21
+ - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
22
+ - `WebSocket` is built-in. Don't use `ws`.
23
+ - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
24
+ - Bun.$`ls` instead of execa.
25
+
26
+ ## Testing
27
+
28
+ Use `bun test` to run tests.
29
+
30
+ ```ts#index.test.ts
31
+ import { test, expect } from "bun:test";
32
+
33
+ test("hello world", () => {
34
+ expect(1).toBe(1);
35
+ });
36
+ ```
37
+
38
+ ## Frontend
39
+
40
+ Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
41
+
42
+ Server:
43
+
44
+ ```ts#index.ts
45
+ import index from "./index.html"
46
+
47
+ Bun.serve({
48
+ routes: {
49
+ "/": index,
50
+ "/api/users/:id": {
51
+ GET: (req) => {
52
+ return new Response(JSON.stringify({ id: req.params.id }));
53
+ },
54
+ },
55
+ },
56
+ // optional websocket support
57
+ websocket: {
58
+ open: (ws) => {
59
+ ws.send("Hello, world!");
60
+ },
61
+ message: (ws, message) => {
62
+ ws.send(message);
63
+ },
64
+ close: (ws) => {
65
+ // handle close
66
+ }
67
+ },
68
+ development: {
69
+ hmr: true,
70
+ console: true,
71
+ }
72
+ })
73
+ ```
74
+
75
+ HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
76
+
77
+ ```html#index.html
78
+ <html>
79
+ <body>
80
+ <h1>Hello, world!</h1>
81
+ <script type="module" src="./frontend.tsx"></script>
82
+ </body>
83
+ </html>
84
+ ```
85
+
86
+ With the following `frontend.tsx`:
87
+
88
+ ```tsx#frontend.tsx
89
+ import React from "react";
90
+
91
+ // import .css files directly and it works
92
+ import './index.css';
93
+
94
+ import { createRoot } from "react-dom/client";
95
+
96
+ const root = createRoot(document.body);
97
+
98
+ export default function Frontend() {
99
+ return <h1>Hello, world!</h1>;
100
+ }
101
+
102
+ root.render(<Frontend />);
103
+ ```
104
+
105
+ Then, run index.ts
106
+
107
+ ```sh
108
+ bun --hot ./index.ts
109
+ ```
110
+
111
+ For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
@@ -0,0 +1,65 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "bun",
9
+ "request": "attach",
10
+ "name": "Debug --only UT",
11
+ // The URL of the WebSocket inspector to attach to.
12
+ // This value can be retrieved by using `bun --inspect`.
13
+ // bun test --inspect-wait=0.0.0.0:6499
14
+ "url": "ws://0.0.0.0:6499/mapi-ut",
15
+ "preLaunchTask": "launch-only-ut"
16
+ },
17
+ {
18
+ "type": "node",
19
+ "request": "launch",
20
+ "name": "Launch Express sample",
21
+ "args": [
22
+ "${workspaceFolder}/samples/express/main.ts"
23
+ ],
24
+ "runtimeArgs": [
25
+ "-r",
26
+ "ts-node/register"
27
+ ],
28
+ "skipFiles": [
29
+ "<node_internals>/**"
30
+ ]
31
+ },
32
+ {
33
+ "type": "node",
34
+ "request": "launch",
35
+ "name": "Launch Graphql sample",
36
+ "args": [
37
+ "${workspaceFolder}/samples/graphql/main.ts"
38
+ ],
39
+ "runtimeArgs": [
40
+ "-r",
41
+ "ts-node/register"
42
+ ],
43
+ "skipFiles": [
44
+ "<node_internals>/**"
45
+ ]
46
+ },
47
+ {
48
+ "type": "node",
49
+ "request": "launch",
50
+ "name": "Launch collections",
51
+ "args": [
52
+ "${workspaceFolder}/src/cli/cli.ts",
53
+ "--xliff",
54
+ "result/target.xlf"
55
+ ],
56
+ "runtimeArgs": [
57
+ "-r",
58
+ "ts-node/register"
59
+ ],
60
+ "skipFiles": [
61
+ "<node_internals>/**"
62
+ ]
63
+ },
64
+ ]
65
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "mochaExplorer.files": ["test/**/*.spec.ts"],
3
+ "mochaExplorer.require": [
4
+ "ts-node/register"
5
+ ],
6
+ "mochaExplorer.debuggerConfig": "Debug Mocha Tests",
7
+ // "mochaExplorer.nodeArgs": "--harmony-async-iteration",
8
+ "files.exclude": {
9
+ "coverage": true,
10
+ "bin": true,
11
+ "node_modules": true,
12
+ "src/compile-cache": true
13
+ }
14
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "launch-only-ut",
6
+ "command": "bun", // Could be any other shell command
7
+ "args": ["test", "--only", "--inspect-wait=ws://0.0.0.0:6499/mapi-ut", "--timeout=0"],
8
+ "type": "shell",
9
+ "isBackground": true,
10
+ "runOptions": {
11
+ "instanceLimit": 1
12
+ },
13
+ // this ensures that the config in launch.json will not wait for this task to finish
14
+ "problemMatcher": {
15
+ "owner": "custom",
16
+ "pattern": {
17
+ "regexp": "_____"
18
+ },
19
+ "background": {
20
+ "activeOnStart": true,
21
+ "beginsPattern": "^.*Listening.*$",
22
+ "endsPattern": "^.*Inspect.*$"
23
+ }
24
+ }
25
+ }
26
+ ]
27
+ }
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # Purpose
2
+
3
+ If like me:
4
+ - you are developping a NodeJS API server which requires internationalization.
5
+ - you find most i18n libraries too complicated for your needs, or requiring a refactoring of your existing architecture
6
+ - you find it painful to propagate the request accepted languages in all your application parts
7
+
8
+ ... then this library might be for you.
9
+
10
+ Read a tutorial to learn how to use this lib [here](https://dev.to/oguimbal/i18n-express-apollo-graphql-server-translation-made-simple-33f5)
11
+
12
+
13
+ # Framework support
14
+
15
+ There are almost-one-liners integrations with the following frameworks:
16
+
17
+ - [Express](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express) => see [this sample](./samples/express/main.ts)
18
+ - [Apollo server express](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express) => see [this sample](./samples/graphql/main.ts)
19
+
20
+ # How this works
21
+
22
+ Install it
23
+
24
+ ```bash
25
+ npm install smartloc --save
26
+ ```
27
+
28
+ Just forget manipulating already translated strings in your code. It is cumbersome, and might leak languages you dont understand in your logs.
29
+
30
+ Smartloc allows you to declare in your code strings like that:
31
+
32
+ ```typescript
33
+ // recommanded: Explicitely specify a string unique ID
34
+ const myString = loc('stringUniqueId')`Hello ${name}, how are you today ?`;
35
+
36
+ // If you are not affraid of occasionally losing some translations when changing your code,
37
+ // then you can use this simpler form:
38
+ const myString = loc`Hello ${name}, how are you today ?`;
39
+ // => An ID will be autogenerated based on this string hash
40
+ // => you might lose translations when changing the original string in code.
41
+ ```
42
+
43
+ Those will give you an instance of `StrLoc` interface.
44
+
45
+ Here is how you can use it:
46
+ ```typescript
47
+ // build a translatable string
48
+ const name = 'world';
49
+ const str = loc`Hello ${name}`; // nb: This one will have an auto-generated id.
50
+
51
+ // Just fake loading translations
52
+ setDefaultLocale('en');
53
+ addLocale('fr', {
54
+ [str.id]: 'Bonjour {0}',
55
+ });
56
+
57
+ // Use the string without language context (logs, ...)
58
+ console.log(str.toString()); // => Hello world
59
+ console.log(JSON.stringify({msg: str})); // => {"msg": "Hello world"}
60
+
61
+ // ... or with language context (when returning a query result, ...)
62
+ console.log(withLocales(['it', 'fr'], () => str.toString())); // => Bonjour world
63
+ console.log(withLocales(['it', 'fr'], () => JSON.stringify({msg: str})); // => {"msg": "Bonjour world"}
64
+ ```
65
+
66
+ As you might see, the translation is NOT performed when you build the string, but when you actually try to send a result to your end user.
67
+
68
+ This allows you to build your app without caring about knowing which language your user accepts.
69
+ The translation will be automatically performed when sending actual json to your user, through a simple middleware (see samples listed in "Framework Support")
70
+
71
+
72
+ # Translating your app
73
+
74
+ ## Generating/updating translations from code
75
+ As an example, if you write your code in english, and you would like to translate your app in French and Deutsch, add the following script to your package.json file:
76
+
77
+ ```typescript
78
+ {
79
+ "scripts": {
80
+ "smartloc": "smartloc collect --format=json --locales=fr-FR,de-DE --defaultLocale=en-US"
81
+ }
82
+ }
83
+ ```
84
+
85
+ Once you have written your code (or each time you have changed it), you can run `npm run smartloc` to create/update your translation files.
86
+
87
+ nb: The `--defaultLocale` argument is optional, and will be infered from your code if you explicitly call `setDefaultLocale()` somewhere.
88
+
89
+ ## Loading available translations on server boot
90
+
91
+ Before serving any request, you must:
92
+ 1) Tell smartloc which is the default locale (the one you wrote your translations in)
93
+ 2) Load other locales
94
+
95
+ For 1), this is straightforward: `setDefaultLocale('en-US')`
96
+
97
+ To load other locales, you have several options:
98
+
99
+ ### Option 1 - Define in code:
100
+ ```typescript
101
+ import {addLocale} from 'smartloc';
102
+
103
+ // you could also pass here an object loaded from your database, or whatever
104
+ addLocale('fr-FR', {
105
+ mySentenceId: 'Une traduite en français',
106
+ });
107
+ ```
108
+
109
+ ### Option 2 - Load a single given file
110
+
111
+ ```typescript
112
+ import { loadAllLocales } from 'smartloc/node';
113
+
114
+ await loadAllLocales('/path/to/my-translation.json', true);
115
+ ```
116
+
117
+ nb: The second argument is 'merge'... if false, all previously loaded translations will be cleared. Else, translations will be merged.
118
+
119
+
120
+ ### Option 3 - Scan a directory for translations
121
+
122
+ ```typescript
123
+ import { loadAllLocales } from 'smartloc/node';
124
+
125
+ await loadAllLocales('/path/to/dir/to/scan', true);
126
+ ```
127
+
128
+ nb: The second argument is 'merge'... if false, all previously loaded translations will be cleared. Else, translations will be merged.
129
+
130
+
131
+
132
+
133
+ ## Supported formats
134
+
135
+ Smartloc cli implements two translation format through the `--format` argument
136
+
137
+ - `--format=json` : JSON translation files
138
+ - `--format=xliff` : XLIFF translation files
139
+
140
+ nb: Smartloc is grouping your translation IDs by category, detected by the first "." in your ID.
141
+
142
+ # Other use cases
143
+
144
+ The `LocStr` interface has [several implementations](./src/core/smartloc.ts):
145
+
146
+ ### Smartloc
147
+ The default one which is returned when using the `loc` tag:
148
+ ```typescript
149
+ return loc`Hello`;
150
+ ```
151
+
152
+ ### MultiLoc
153
+ If you wish to declare all translations directly in your code:
154
+ ```typescript
155
+ return new MultiLoc({
156
+ en: 'Hello',
157
+ fr: 'Bonjour',
158
+ });
159
+ ```
160
+
161
+ ### SingleLoc
162
+ If you wish to declare a string that is the same in all languages, but which is typed as a `LocStr`:
163
+ ```typescript
164
+ return new SingleLoc('Typescript');
165
+ ```
166
+
167
+ ### TransformedLoc
168
+ Sometimes, you will want to apply transformations to your final string.
169
+ You can do that using the `.transform()` method available on `LocStr`, which will return you a transformed translatable string.
170
+
171
+ ```typescript
172
+ return loc`Some string wich can contain html`
173
+ .transform(x => escapeHtml(x)); // apply a transformation
174
+ ```
175
+
176
+ ## Array of LocStr
177
+ When you have an array of smartloc strings that you want to join, you can use the `LocStringArray` class:
178
+
179
+ ```typescript
180
+ const array = new LocStringArray([loc`Hello`, loc`world`]);
181
+
182
+ const str = array.join(' ').transform(x => x + ' !');
183
+
184
+ console.log(str.toString('en')); // => Hello world !
185
+ console.log(str.toString('fr')); // => Bonjour monde !
186
+ ```
187
+
188
+ ## Serialization in an untranslated form
189
+
190
+ Somtimes, you will want to serialize an arbitrary `LocStr` in its untranslated form (to store a localizable sentence in a DB, for instance).
191
+
192
+ In this case, you can serialize it like that:
193
+
194
+ ```typescript
195
+ import {loc, MultiLoc, withSerializationContext} from 'smartloc';
196
+ const sampleObject = {
197
+ reference: loc('stringId')`Hello {world}`,
198
+ multi: new MultiLoc({ en: 'A string', fr: 'Une chaine' }),
199
+ };
200
+
201
+ // serialize
202
+ const serialized = withSerializationContext(() => JSON.stringify(sampleObject));
203
+
204
+ // store ... nb: it will look like {"reference": "i18n/id:stringId", "multi": {"i18n:fr": "A string", "i18n:en": "Une chaine"}}
205
+ storeInDb(serialized);
206
+ ```
207
+
208
+ You can deserialize it back to translatable instance later like that:
209
+
210
+ ```typescript
211
+ import {toLocalizable} from 'smartloc';
212
+ const obj = loadFromDb();
213
+
214
+ // get back a translatable intance
215
+ const serializable = toLocalizable(obj);
216
+ ```
217
+
218
+ ## Cloning
219
+ Beware, if you deep-clone an object containing smartloc string instances, you must:
220
+ - Clone the object prototype
221
+ - Clone symbol properties
222
+
223
+ ... or you could just ignore cloning smartloc strings altogether (they are immutable anyway): You can detect them using the `isLocStr()` method and skip them when performing your deep clone.
224
+
225
+ NB: Of course, if you clone your object using `JSON.parse(JSON.stringify(obj))`, then you will lose translatability (smartloc strings will be translated as strings in your default language).
@@ -0,0 +1,225 @@
1
+ # Purpose
2
+
3
+ If like me:
4
+ - you are developping a NodeJS API server which requires internationalization.
5
+ - you find most i18n libraries too complicated for your needs, or requiring a refactoring of your existing architecture
6
+ - you find it painful to propagate the request accepted languages in all your application parts
7
+
8
+ ... then this library might be for you.
9
+
10
+ Read a tutorial to learn how to use this lib [here](https://dev.to/oguimbal/i18n-express-apollo-graphql-server-translation-made-simple-33f5)
11
+
12
+
13
+ # Framework support
14
+
15
+ There are almost-one-liners integrations with the following frameworks:
16
+
17
+ - [Express](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express) => see [this sample](./samples/express/main.ts)
18
+ - [Apollo server express](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express) => see [this sample](./samples/graphql/main.ts)
19
+
20
+ # How this works
21
+
22
+ Install it
23
+
24
+ ```bash
25
+ npm install smartloc --save
26
+ ```
27
+
28
+ Just forget manipulating already translated strings in your code. It is cumbersome, and might leak languages you dont understand in your logs.
29
+
30
+ Smartloc allows you to declare in your code strings like that:
31
+
32
+ ```typescript
33
+ // recommanded: Explicitely specify a string unique ID
34
+ const myString = loc('stringUniqueId')`Hello ${name}, how are you today ?`;
35
+
36
+ // If you are not affraid of occasionally losing some translations when changing your code,
37
+ // then you can use this simpler form:
38
+ const myString = loc`Hello ${name}, how are you today ?`;
39
+ // => An ID will be autogenerated based on this string hash
40
+ // => you might lose translations when changing the original string in code.
41
+ ```
42
+
43
+ Those will give you an instance of `StrLoc` interface.
44
+
45
+ Here is how you can use it:
46
+ ```typescript
47
+ // build a translatable string
48
+ const name = 'world';
49
+ const str = loc`Hello ${name}`; // nb: This one will have an auto-generated id.
50
+
51
+ // Just fake loading translations
52
+ setDefaultLocale('en');
53
+ addLocale('fr', {
54
+ [str.id]: 'Bonjour {0}',
55
+ });
56
+
57
+ // Use the string without language context (logs, ...)
58
+ console.log(str.toString()); // => Hello world
59
+ console.log(JSON.stringify({msg: str})); // => {"msg": "Hello world"}
60
+
61
+ // ... or with language context (when returning a query result, ...)
62
+ console.log(withLocales(['it', 'fr'], () => str.toString())); // => Bonjour world
63
+ console.log(withLocales(['it', 'fr'], () => JSON.stringify({msg: str})); // => {"msg": "Bonjour world"}
64
+ ```
65
+
66
+ As you might see, the translation is NOT performed when you build the string, but when you actually try to send a result to your end user.
67
+
68
+ This allows you to build your app without caring about knowing which language your user accepts.
69
+ The translation will be automatically performed when sending actual json to your user, through a simple middleware (see samples listed in "Framework Support")
70
+
71
+
72
+ # Translating your app
73
+
74
+ ## Generating/updating translations from code
75
+ As an example, if you write your code in english, and you would like to translate your app in French and Deutsch, add the following script to your package.json file:
76
+
77
+ ```typescript
78
+ {
79
+ "scripts": {
80
+ "smartloc": "smartloc collect --format=json --locales=fr-FR,de-DE --defaultLocale=en-US"
81
+ }
82
+ }
83
+ ```
84
+
85
+ Once you have written your code (or each time you have changed it), you can run `npm run smartloc` to create/update your translation files.
86
+
87
+ nb: The `--defaultLocale` argument is optional, and will be infered from your code if you explicitly call `setDefaultLocale()` somewhere.
88
+
89
+ ## Loading available translations on server boot
90
+
91
+ Before serving any request, you must:
92
+ 1) Tell smartloc which is the default locale (the one you wrote your translations in)
93
+ 2) Load other locales
94
+
95
+ For 1), this is straightforward: `setDefaultLocale('en-US')`
96
+
97
+ To load other locales, you have several options:
98
+
99
+ ### Option 1 - Define in code:
100
+ ```typescript
101
+ import {addLocale} from 'smartloc';
102
+
103
+ // you could also pass here an object loaded from your database, or whatever
104
+ addLocale('fr-FR', {
105
+ mySentenceId: 'Une traduite en français',
106
+ });
107
+ ```
108
+
109
+ ### Option 2 - Load a single given file
110
+
111
+ ```typescript
112
+ import { loadAllLocales } from 'smartloc/node';
113
+
114
+ await loadAllLocales('/path/to/my-translation.json', true);
115
+ ```
116
+
117
+ nb: The second argument is 'merge'... if false, all previously loaded translations will be cleared. Else, translations will be merged.
118
+
119
+
120
+ ### Option 3 - Scan a directory for translations
121
+
122
+ ```typescript
123
+ import { loadAllLocales } from 'smartloc/node';
124
+
125
+ await loadAllLocales('/path/to/dir/to/scan', true);
126
+ ```
127
+
128
+ nb: The second argument is 'merge'... if false, all previously loaded translations will be cleared. Else, translations will be merged.
129
+
130
+
131
+
132
+
133
+ ## Supported formats
134
+
135
+ Smartloc cli implements two translation format through the `--format` argument
136
+
137
+ - `--format=json` : JSON translation files
138
+ - `--format=xliff` : XLIFF translation files
139
+
140
+ nb: Smartloc is grouping your translation IDs by category, detected by the first "." in your ID.
141
+
142
+ # Other use cases
143
+
144
+ The `LocStr` interface has [several implementations](./src/core/smartloc.ts):
145
+
146
+ ### Smartloc
147
+ The default one which is returned when using the `loc` tag:
148
+ ```typescript
149
+ return loc`Hello`;
150
+ ```
151
+
152
+ ### MultiLoc
153
+ If you wish to declare all translations directly in your code:
154
+ ```typescript
155
+ return new MultiLoc({
156
+ en: 'Hello',
157
+ fr: 'Bonjour',
158
+ });
159
+ ```
160
+
161
+ ### SingleLoc
162
+ If you wish to declare a string that is the same in all languages, but which is typed as a `LocStr`:
163
+ ```typescript
164
+ return new SingleLoc('Typescript');
165
+ ```
166
+
167
+ ### TransformedLoc
168
+ Sometimes, you will want to apply transformations to your final string.
169
+ You can do that using the `.transform()` method available on `LocStr`, which will return you a transformed translatable string.
170
+
171
+ ```typescript
172
+ return loc`Some string wich can contain html`
173
+ .transform(x => escapeHtml(x)); // apply a transformation
174
+ ```
175
+
176
+ ## Array of LocStr
177
+ When you have an array of smartloc strings that you want to join, you can use the `LocStringArray` class:
178
+
179
+ ```typescript
180
+ const array = new LocStringArray([loc`Hello`, loc`world`]);
181
+
182
+ const str = array.join(' ').transform(x => x + ' !');
183
+
184
+ console.log(str.toString('en')); // => Hello world !
185
+ console.log(str.toString('fr')); // => Bonjour monde !
186
+ ```
187
+
188
+ ## Serialization in an untranslated form
189
+
190
+ Somtimes, you will want to serialize an arbitrary `LocStr` in its untranslated form (to store a localizable sentence in a DB, for instance).
191
+
192
+ In this case, you can serialize it like that:
193
+
194
+ ```typescript
195
+ import {loc, MultiLoc, withSerializationContext} from 'smartloc';
196
+ const sampleObject = {
197
+ reference: loc('stringId')`Hello {world}`,
198
+ multi: new MultiLoc({ en: 'A string', fr: 'Une chaine' }),
199
+ };
200
+
201
+ // serialize
202
+ const serialized = withSerializationContext(() => JSON.stringify(sampleObject));
203
+
204
+ // store ... nb: it will look like {"reference": "i18n/id:stringId", "multi": {"i18n:fr": "A string", "i18n:en": "Une chaine"}}
205
+ storeInDb(serialized);
206
+ ```
207
+
208
+ You can deserialize it back to translatable instance later like that:
209
+
210
+ ```typescript
211
+ import {toLocalizable} from 'smartloc';
212
+ const obj = loadFromDb();
213
+
214
+ // get back a translatable intance
215
+ const serializable = toLocalizable(obj);
216
+ ```
217
+
218
+ ## Cloning
219
+ Beware, if you deep-clone an object containing smartloc string instances, you must:
220
+ - Clone the object prototype
221
+ - Clone symbol properties
222
+
223
+ ... or you could just ignore cloning smartloc strings altogether (they are immutable anyway): You can detect them using the `isLocStr()` method and skip them when performing your deep clone.
224
+
225
+ NB: Of course, if you clone your object using `JSON.parse(JSON.stringify(obj))`, then you will lose translatability (smartloc strings will be translated as strings in your default language).