diff --git a/README.md b/README.md
index d8356f7..7f4accc 100644
--- a/README.md
+++ b/README.md
@@ -52,12 +52,13 @@ npm install qwik-speak --save-dev
```
### Getting the translation
```jsx
-import { translate as t } from 'qwik-speak';
+import { translate as t, plural as p } from 'qwik-speak';
export default component$(() => {
return (
<>
-
{t('app.title', { name: 'Qwik Speak' })}
{/* I'm Qwik Speak */}
+ {t('app.title', { name: 'Qwik Speak' })}
{/* I'm Qwik Speak */}
+ {p(1, 'app.devs')}
{/* One software developer */}
>
);
});
@@ -94,7 +95,11 @@ Assets will be loaded through the implementation of `getTranslation$` function b
```json
{
"app": {
- "title": "I'm {{name}}"
+ "title": "I'm {{name}}",
+ "devs": {
+ "one": "One software developer",
+ "other": "{{value}} software developers"
+ }
}
}
```
@@ -216,10 +221,13 @@ and optionally contains:
- `translate(keys: string | string[], params?: any, ctx?: SpeakState, lang?: string)`
Translates a key or an array of keys
-- `formatDate(value: any, options?: Intl.DateTimeFormatOptions, locale?: SpeakLocale, lang?: string, timeZone?: string)`
+- `plural(value: number | string, prefix?: string, options?: Intl.PluralRulesOptions, ctx?: SpeakState, lang?: string)`
+Gets the plural by a number
+
+- `formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions, locale?: SpeakLocale, lang?: string, timeZone?: string)`
Formats a date
-- `formatNumber(value: any, options?: Intl.NumberFormatOptions, locale?: SpeakLocale, lang?: string, currency?: string)`
+- `formatNumber(value: number | string, options?: Intl.NumberFormatOptions, locale?: SpeakLocale, lang?: string, currency?: string)`
Formats a number
- `changeLocale(newLocale: SpeakLocale, ctx: SpeakState, location?: RouteLocation)`
@@ -255,7 +263,9 @@ npm run build
```
## What's new
-> Released v0.0.10
+> Released v0.0.11
+
+> Add plural function
## License
MIT
diff --git a/public/i18n/en-US/home.json b/public/i18n/en-US/home.json
index 6fe6b56..858df55 100644
--- a/public/i18n/en-US/home.json
+++ b/public/i18n/en-US/home.json
@@ -1,6 +1,15 @@
{
"home": {
"greeting": "Hi! I am {{name}}",
- "text": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps"
+ "text": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps",
+ "params": "Parameters",
+ "tags": "Html tags",
+ "plural": "Plural",
+ "dates": "Dates",
+ "numbers": "Numbers & currencies",
+ "devs": {
+ "one": "One software developer",
+ "other": "{{value}} software developers"
+ }
}
}
\ No newline at end of file
diff --git a/public/i18n/it-IT/home.json b/public/i18n/it-IT/home.json
index b21df18..c69310e 100644
--- a/public/i18n/it-IT/home.json
+++ b/public/i18n/it-IT/home.json
@@ -1,6 +1,15 @@
{
"home": {
"greeting": "Ciao! Sono {{name}}",
- "text": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik"
+ "text": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik",
+ "params": "Parametri",
+ "tags": "Tag Html",
+ "plural": "Plurale",
+ "dates": "Date",
+ "numbers": "Numeri e valute",
+ "devs": {
+ "one": "Uno sviluppatore software",
+ "other": "{{ value }} sviluppatori software"
+ }
}
}
\ No newline at end of file
diff --git a/src/app/components/head/head.tsx b/src/app/components/head/head.tsx
index dc117b4..ea80115 100644
--- a/src/app/components/head/head.tsx
+++ b/src/app/components/head/head.tsx
@@ -12,7 +12,7 @@ export const Head = component$(() => {
{/* Translate title */}
{t(head.title, { name: 'Qwik Speak' })}
-
+
{/* Translate description */}
diff --git a/src/app/components/header/header.css b/src/app/components/header/header.css
index 1699a32..695c97c 100644
--- a/src/app/components/header/header.css
+++ b/src/app/components/header/header.css
@@ -1,8 +1,17 @@
header {
+ background-color: #0093ee;
+}
+
+header .header-inner {
display: grid;
grid-template-columns: 1fr auto;
padding: 10px;
- background-color: #0093ee;
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.full-screen header .header-inner {
+ max-width: 100%;
}
header a {
@@ -21,6 +30,21 @@ header .active {
background-color: #ffffff30;
}
+.theme-toggle {
+ background: transparent;
+ width: 30px;
+ border: none;
+ cursor: pointer;
+}
+
+.theme-light .theme-toggle::before {
+ content: '☽';
+}
+
+.theme-dark .theme-toggle::before {
+ content: '☀';
+}
+
.change-locale {
padding: 8px;
color: white;
diff --git a/src/app/components/header/header.tsx b/src/app/components/header/header.tsx
index 439c340..76bb147 100644
--- a/src/app/components/header/header.tsx
+++ b/src/app/components/header/header.tsx
@@ -20,20 +20,22 @@ export const Header = component$(() => {
return (
);
});
diff --git a/src/app/routes/[...lang]/index.tsx b/src/app/routes/[...lang]/index.tsx
index 820a19d..b190d11 100644
--- a/src/app/routes/[...lang]/index.tsx
+++ b/src/app/routes/[...lang]/index.tsx
@@ -1,6 +1,7 @@
import { component$ } from '@builder.io/qwik';
import { DocumentHead } from '@builder.io/qwik-city';
import { translate as t } from '../../../library/translate';
+import { plural as p } from '../../../library/plural';
import { formatDate as fd } from '../../../library/format-date';
import { formatNumber as fn } from '../../../library/format-number';
import { useSpeakLocale } from '../../../library/use-functions';
@@ -14,13 +15,20 @@ export const Home = component$(() => {
{t('app.title')}
{t('app.subtitle')}
- {/* Params */}
+ {t('home.params')}
{t('home.greeting', { name: 'Qwik Speak' })}
- {/* Html tags */}
+
+ {t('home.tags')}
- {/* Dates */}
+
+ {t('home.plural')}
+ {p(1, 'home.devs')}
+ {p(2, 'home.devs')}
+
+ {t('home.dates')}
{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}
- {/* Numbers */}
+
+ {t('home.numbers')}
{fn(1000000)}
{fn(1000000, { style: 'currency' })}
{fn(1, { style: 'unit', unit: units['length'] })}
diff --git a/src/app/routes/layout.tsx b/src/app/routes/layout.tsx
index 9061f80..907cea5 100644
--- a/src/app/routes/layout.tsx
+++ b/src/app/routes/layout.tsx
@@ -5,12 +5,12 @@ import { Header } from '../components/header/header';
export default component$(() => {
return (
- <>
+
- >
+
);
});
diff --git a/src/global.css b/src/global.css
index fa31d13..219c7a3 100644
--- a/src/global.css
+++ b/src/global.css
@@ -1,15 +1,24 @@
-html {
- line-height: 1.5;
- -webkit-text-size-adjust: 100%;
- -moz-tab-size: 4;
- -o-tab-size: 4;
- tab-size: 4;
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
- 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
- 'Segoe UI Symbol', 'Noto Color Emoji';
-}
-
body {
+ margin: 0;
padding: 0;
- line-height: inherit;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
+ sans-serif;
+}
+
+main {
+ padding: 10px 20px;
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.full-screen main {
+ max-width: 100%;
+}
+
+a {
+ color: #006eb3;
+}
+
+a:hover {
+ text-decoration: none;
}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index b882596..3e40f78 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -25,6 +25,7 @@ export { Speak } from './library/speak';
// Functions
export { changeLocale } from './library/change-locale';
export { translate } from './library/translate';
+export { plural } from './library/plural';
export { formatNumber } from './library/format-number';
export { formatDate } from './library/format-date';
// Core functions
diff --git a/src/library/format-date.ts b/src/library/format-date.ts
index 9544673..692d62a 100644
--- a/src/library/format-date.ts
+++ b/src/library/format-date.ts
@@ -13,7 +13,7 @@ import { toDate } from './utils';
* @returns The formatted date
*/
export const formatDate = (
- value: any,
+ value: Date | number | string,
options?: Intl.DateTimeFormatOptions,
locale?: SpeakLocale,
lang?: string,
diff --git a/src/library/format-number.ts b/src/library/format-number.ts
index c385cfa..4b79b52 100644
--- a/src/library/format-number.ts
+++ b/src/library/format-number.ts
@@ -12,7 +12,7 @@ import { toNumber } from './utils';
* @returns The formatted number
*/
export const formatNumber = (
- value: any,
+ value: number | string,
options?: Intl.NumberFormatOptions,
locale?: SpeakLocale,
lang?: string,
diff --git a/src/library/plural.ts b/src/library/plural.ts
new file mode 100644
index 0000000..002dc0f
--- /dev/null
+++ b/src/library/plural.ts
@@ -0,0 +1,34 @@
+import { translate } from './translate';
+import type { SpeakState } from './types';
+import { useSpeakContext } from './use-functions';
+import { toNumber } from './utils';
+
+/**
+ * Get the plural by a number.
+ * The value is passed as a parameter to the translate function
+ * @param value A number or a string
+ * @param prefix Optional prefix for the key
+ * @param options Intl PluralRulesOptions object
+ * @param ctx Optional Speak context to be provided outside the component$
+ * @param lang Optional language if different from the current one
+ * @returns The translation for the plural
+ */
+export const plural = (
+ value: number | string,
+ prefix?: string,
+ options?: Intl.PluralRulesOptions,
+ ctx?: SpeakState,
+ lang?: string
+): string | any => {
+ ctx = ctx ?? useSpeakContext();
+ const { locale, config } = ctx;
+
+ lang = lang ?? locale.lang;
+
+ value = toNumber(value);
+
+ const rule = new Intl.PluralRules(lang, options).select(value);
+ const key = prefix ? `${prefix}${config.keySeparator}${rule}` : rule;
+
+ return translate(key, { value }, ctx, lang);
+};
diff --git a/src/library/qwik-speak.tsx b/src/library/qwik-speak.tsx
index 996908e..a65c4c9 100644
--- a/src/library/qwik-speak.tsx
+++ b/src/library/qwik-speak.tsx
@@ -40,7 +40,7 @@ export const QwikSpeak = component$((props: QwikSpeakProps) => {
defaultLocale: props.config.defaultLocale,
supportedLocales: props.config.supportedLocales,
assets: [...props.config.assets], // Shallow copy
- keySeparator: props.config.keySeparator
+ keySeparator: props.config.keySeparator || '.'
},
translateFn: resolvedTranslateFn
}, { recursive: true });
diff --git a/src/tests/config.ts b/src/tests/config.ts
index c3c82a8..4c633b2 100644
--- a/src/tests/config.ts
+++ b/src/tests/config.ts
@@ -7,7 +7,9 @@ const translationData: Translation = {
testParams: 'Test {{param}}',
nested: {
test: 'Test'
- }
+ },
+ one: 'One software developer',
+ other: '{{value}} software developers'
},
'it-IT': {
test: 'Prova'
diff --git a/src/tests/core.test.ts b/src/tests/core.test.ts
index bd42db2..286a80d 100644
--- a/src/tests/core.test.ts
+++ b/src/tests/core.test.ts
@@ -57,13 +57,13 @@ describe('core', () => {
});
});
test('getValue', () => {
- let value = getValue('KEY1', { KEY1: 'key1', KEY2: 'key2' }, '.');
+ let value = getValue('KEY1', { KEY1: 'key1', KEY2: 'key2' });
expect(value).toBe('key1');
- value = getValue('SUBKEY1.AA', { KEY1: 'key1', SUBKEY1: { AA: 'aa' } }, '.');
+ value = getValue('SUBKEY1.AA', { KEY1: 'key1', SUBKEY1: { AA: 'aa' } });
expect(value).toBe('aa');
- value = getValue('SUBKEY1', { KEY1: 'key1', SUBKEY1: { AA: 'aa' } }, '.');
+ value = getValue('SUBKEY1', { KEY1: 'key1', SUBKEY1: { AA: 'aa' } });
expect(value).toBeUndefined();
- value = getValue('SUBKEY1.BB', { KEY1: 'key1', SUBKEY1: { AA: 'aa' } }, '.');
+ value = getValue('SUBKEY1.BB', { KEY1: 'key1', SUBKEY1: { AA: 'aa' } });
expect(value).toBeUndefined();
});
test('handleParams', () => {
diff --git a/src/tests/plural.test.ts b/src/tests/plural.test.ts
new file mode 100644
index 0000000..966497e
--- /dev/null
+++ b/src/tests/plural.test.ts
@@ -0,0 +1,13 @@
+import { plural as p } from '../library/plural';
+import { ctx } from './config';
+
+describe('plural function', () => {
+ test('one', () => {
+ const value = p(1, '', {}, ctx);
+ expect(value).toBe('One software developer');
+ });
+ test('other', () => {
+ const value = p(2, '', {}, ctx);
+ expect(value).toBe('2 software developers');
+ });
+});