diff --git a/README.md b/README.md
index 1ba369e..63d32a5 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,7 @@
Generic i18n library managing the fullstack interaction in a CI/CD pace. The dictionaries are stored in a DB edited by the translators through a(/the same) web application - managing translation errors, missing keys, ...
-- 1.0.x - ~~alpha~~
- 1.1.x - [beta](https://www.youtube.com/watch?v=1gSZfX91zYk)
-- 1.1.5 - The library finally has well-set entry points and export bundles
-- 1.1.8 - UMD-able
[![view on npm](https://badgen.net/npm/v/omni18n)](https://www.npmjs.org/package/omni18n)
[![npm module downloads](https://badgen.net/npm/dt/omni18n)](https://www.npmjs.org/package/omni18n)
diff --git a/docs/umd.md b/docs/umd.md
index 5a846cd..e685297 100644
--- a/docs/umd.md
+++ b/docs/umd.md
@@ -59,11 +59,7 @@ A script-less way to use the library is by providing the arguments (`locales`, `
We speak about the "blink" when the page is just loaded and still displayed in its native language for half a second before being translated in the target language.
-[_For now_](#todo), the solution needs to specify manually all the locales who shouldn't blink.
-
-```html
-
-```
+[_For now_](#todo), the best solution is to let the texts empty when an `i18n` attribute is specified
Also, as many mobile webapp tend to let the resource loading at the end of the page, hurrying the translation by inserting a `translatePage` between the page content and the late loads (audio/scripts/...) can show useful.
diff --git a/src/client/helpers.ts b/src/client/helpers.ts
index be288f5..2d77693 100644
--- a/src/client/helpers.ts
+++ b/src/client/helpers.ts
@@ -72,13 +72,17 @@ export function translate(context: TContext, args: any[]): string {
export function translator(context: TContext): Translator {
Object.freeze(context)
+ function escapeArgs(args: any[]) {
+ // TODO? replace more that \ and :
+ return args.map((a) => (typeof a === 'string' ? a.replace(/([\\:])/g, '\\$1') : a))
+ }
const translation = context.key
? function (...args: any[]): string {
- return translate(context, args)
+ return translate(context, escapeArgs(args))
}
: function (key?: TextKey, ...args: any[]): string {
if (!key) throw new TranslationError('Root translator called without key')
- if (typeof key === 'string') return translate({ ...context, key }, args)
+ if (typeof key === 'string') return translate({ ...context, key }, escapeArgs(args))
return translate({ ...context, key }, args)
}
const primitive = new Proxy(translation, {
@@ -232,3 +236,9 @@ export function bulkDictionary(
}
return dictionaryToTranslation(current, key)
}
+
+export function split2(s: string, sep: string) {
+ const ndx = s.indexOf(sep)
+ if (ndx === -1) return [s]
+ return [s.slice(0, ndx), s.slice(ndx + sep.length)]
+}
diff --git a/src/client/interpolation.ts b/src/client/interpolation.ts
index 8cb14c4..d52cbcf 100644
--- a/src/client/interpolation.ts
+++ b/src/client/interpolation.ts
@@ -1,4 +1,4 @@
-import { reportMissing, reportError, translate } from './helpers'
+import { reportMissing, reportError, translate, split2 } from './helpers'
import { TContext, TranslationError } from './types'
export const formats: Record<'date' | 'number' | 'relative', Record> = {
@@ -165,9 +165,9 @@ function objectArgument(
if (typeof arg === 'object') return arg
// Here we throw as it means the code gave a wrong argument
if (typeof arg !== 'string') throw new TranslationError(`Invalid argument type: ${typeof arg}`)
- if (!/:/.test(arg)) return arg
+ if (!/:[^\/\\]/.test(arg)) return arg
return Object.fromEntries(
- arg.split(',').map((part) => part.split(':', 2).map((part) => unescape(part.trim())))
+ arg.split(',').map((part) => split2(part, ':').map((part) => unescape(part.trim())))
)
}
@@ -189,13 +189,17 @@ export function interpolate(context: TContext, text: string, args: any[]): strin
const escapements: Record = { '/': '/' },
unescapements: Record = {}
let escapementCounter = 0
- placeholder = placeholder.replace(/\\(.)/g, (_, c) => {
- if (!escapements[c]) {
- unescapements[escapementCounter] = c
- escapements[c] = '\u0004' + escapementCounter++ + '\u0005'
- }
- return escapements[c]
- })
+ function escaped(s: string) {
+ return s.replace(/\\(.)/g, (_, c) => {
+ if (!escapements[c]) {
+ unescapements[escapementCounter] = c
+ escapements[c] = '\u0004' + escapementCounter++ + '\u0005'
+ }
+ return escapements[c]
+ })
+ }
+ placeholder = escaped(placeholder)
+ args = args.map((arg) => (typeof arg === 'string' ? escaped(arg) : arg))
function unescape(s: string) {
return s
.replace(/\u0003/g, '\n')
diff --git a/src/umd/client.ts b/src/umd/client.ts
index faa721e..5b13d69 100644
--- a/src/umd/client.ts
+++ b/src/umd/client.ts
@@ -1,3 +1,4 @@
+import { split2 } from 'src/client/helpers'
import {
I18nClient,
CondensedDictionary,
@@ -47,7 +48,7 @@ export function translatePage() {
for (const element of document.querySelectorAll('[i18n]')) {
const parts = element.getAttribute('i18n')!.split(',')
for (const part of parts) {
- const [attr, key] = part.split(':', 2).map((k) => k.trim())
+ const [attr, key] = split2(part, ':').map((k) => k.trim())
if (attr === 'html') element.innerHTML = T[key]()
if (key) element.setAttribute(attr, T[key]())
else element.textContent = T[attr]()
@@ -59,7 +60,7 @@ export function translatePage() {
for (const element of document.querySelectorAll('[i18n]')) {
const parts = element.getAttribute('i18n')!.split(',')
for (const part of parts) {
- const [attr, key] = part.split(':', 2).map((k) => k.trim())
+ const [attr, key] = split2(part, ':').map((k) => k.trim())
if (attr === 'html' || !key) element.textContent = ''
}
}
@@ -80,7 +81,7 @@ export function translatePage() {
localeName = new Intl.DisplayNames(locale, { type: 'language' }).of(locale),
selected = usedLocale === locale ? 'selected' : ''
selectionList.push(`
-