diff --git a/README.md b/README.md
index 6c4058a..464509b 100644
--- a/README.md
+++ b/README.md
@@ -7,4 +7,26 @@ React translation library built with the Bun 🐰 runtime and Cursor AI 🖱️
- Built for client-side Serverless architectures on the web
- Supports React Native
- `` component to render translations
-- Replacements in translations: {}
+- Replacements with `{}` in translations
+
+## Usage
+
+```ts
+import { translations } from 'epic-language'
+
+const { translate } = translations(
+ // Initial translations in default language.
+ {
+ title: 'My Title',
+ description: 'This is the description.',
+ counter: 'Count: {}',
+ },
+ // Route to load translations for user language.
+ '/api/translations',
+ // Callback for when translations have been loaded.
+ onLoad,
+)
+
+translate('title') // My Title
+translate('counter', '5') // Counter: 5
+```
diff --git a/index.ts b/index.ts
index dc10c80..b5cde06 100644
--- a/index.ts
+++ b/index.ts
@@ -3,6 +3,7 @@ import { log } from './helper'
type Sheet = { [key: string]: string }
type Sheets = { [key in Language]?: Sheet }
+type Replacement = string | number
const sheets: Sheets = {}
@@ -40,25 +41,44 @@ async function loadSheet(apiRoute: string, onLoad: () => void, defaultLanguage:
})
}
+function insertReplacements(translation: string, replacements?: Replacement | Replacement[]) {
+ if (!replacements) return translation
+
+ if (!Array.isArray(replacements)) {
+ // eslint-disable-next-line no-param-reassign
+ replacements = [replacements]
+ }
+
+ let result = translation
+ for (let index = 0; index < replacements.length; index += 1) {
+ result = result.replace('{}', String(replacements[index]))
+ }
+ return result
+}
+
// defaultLanguage is the language in the standard translation that's always loaded.
export function translations(
defaultTranslations: T,
apiRoute: string,
- onLoad: () => void,
+ onLoad: () => void = () => {},
defaultLanguage: Language = Language.en,
) {
sheets[defaultLanguage] = defaultTranslations
loadSheet(apiRoute, onLoad, defaultLanguage)
- function translate(key: keyof T, language: Language = defaultLanguage) {
+ function translate(
+ key: keyof T,
+ replacements?: Replacement | Replacement[],
+ language: Language = defaultLanguage,
+ ) {
const sheet = sheets[language]
if (!has(sheet, key)) {
log(`Translation for key "${String(key)}" is missing`)
if (Object.hasOwn(sheets[defaultLanguage], key)) return sheets[defaultLanguage][key as string]
return key
}
- return sheet[key as string]
+ return insertReplacements(sheet[key as string], replacements)
}
return { translate }
diff --git a/test/basic.test.tsx b/test/basic.test.tsx
index 07383fb..0717b0a 100644
--- a/test/basic.test.tsx
+++ b/test/basic.test.tsx
@@ -1,3 +1,5 @@
+///
+
import React from 'react'
import { test, expect, mock, spyOn } from 'bun:test'
import { render } from '@testing-library/react'
@@ -9,6 +11,7 @@ GlobalRegistrator.register()
console.log = log // Restore log to show up in tests during development.
const windowLanguageSpy = spyOn(window, 'navigator')
+// @ts-ignore
windowLanguageSpy.mockImplementation(() => ({ language: 'en_US' }))
test('Can render a basic app.', async () => {
@@ -32,7 +35,7 @@ test('Can render a basic app.', async () => {
})
test('Translates key in initially provided language.', () => {
- const onLoad = mock()
+ const onLoad = mock(() => {})
const { translate } = translations(
{ title: 'My Title', description: 'This is the description.' },
'/api/translations',
@@ -49,7 +52,7 @@ test('Translates key in initially provided language.', () => {
test('Symbols or numbers cannot be used as keys.', () => {
// TODO doesn't seem possible to restrict keys to string when using generic type with extends.
- const onLoad = mock()
+ const onLoad = mock(() => {})
const symbol = Symbol('test')
const { translate } = translations(
{ [symbol]: 'My Symbol', 5: 'My Number' },
@@ -64,3 +67,10 @@ test('Symbols or numbers cannot be used as keys.', () => {
expect(translate('missing')).toBe('missing')
expect(onLoad).toHaveBeenCalled()
})
+
+test('Replacements are inserted.', () => {
+ const { translate } = translations({ counter: 'Count: {}' }, '/api/translations')
+
+ expect(translate('counter', '123')).toBe('Count: 123')
+ expect(translate('counter', 456)).toBe('Count: 456')
+})