Skip to content

Commit

Permalink
Feat: add plural function
Browse files Browse the repository at this point in the history
  • Loading branch information
robisim74 committed Sep 4, 2022
1 parent 7750e96 commit 80681d5
Show file tree
Hide file tree
Showing 17 changed files with 171 additions and 50 deletions.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<h1>{t('app.title', { name: 'Qwik Speak' })}</h1> {/* I'm Qwik Speak */}
<p>{t('app.title', { name: 'Qwik Speak' })}</p> {/* I'm Qwik Speak */}
<p>{p(1, 'app.devs')}</p> {/* One software developer */}
</>
);
});
Expand Down Expand Up @@ -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"
}
}
}
```
Expand Down Expand Up @@ -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)`
Expand Down Expand Up @@ -255,7 +263,9 @@ npm run build
```

## What's new
> Released v0.0.10
> Released v0.0.11
> Add plural function
## License
MIT
11 changes: 10 additions & 1 deletion public/i18n/en-US/home.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
{
"home": {
"greeting": "Hi! I am {{name}}",
"text": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>"
"text": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>",
"params": "Parameters",
"tags": "Html tags",
"plural": "Plural",
"dates": "Dates",
"numbers": "Numbers & currencies",
"devs": {
"one": "One software developer",
"other": "{{value}} software developers"
}
}
}
11 changes: 10 additions & 1 deletion public/i18n/it-IT/home.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
{
"home": {
"greeting": "Ciao! Sono {{name}}",
"text": "<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>"
"text": "<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>",
"params": "Parametri",
"tags": "Tag Html",
"plural": "Plurale",
"dates": "Date",
"numbers": "Numeri e valute",
"devs": {
"one": "Uno sviluppatore software",
"other": "{{ value }} sviluppatori software"
}
}
}
2 changes: 1 addition & 1 deletion src/app/components/head/head.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const Head = component$(() => {

{/* Translate title */}
<title>{t(head.title, { name: 'Qwik Speak' })}</title>

<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="canonical" href={loc.href} />

{/* Translate description */}
Expand Down
26 changes: 25 additions & 1 deletion src/app/components/header/header.css
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;
Expand Down
30 changes: 16 additions & 14 deletions src/app/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@ export const Header = component$(() => {

return (
<header>
<section>
<a href="/">Qwik Speak ⚡️</a>
</section>
<nav>
<a href={getHref('/')}
class={{ active: pathname.endsWith('/') || config.supportedLocales.some(x => pathname.endsWith(x.lang)) }}>
{t('app.nav.home')}
</a>
<a href={getHref('/page')}
class={{ active: pathname.endsWith('/page') }}>
{t('app.nav.page')}
</a>
</nav>
<ChangeLocale />
<div class="header-inner">
<section class="logo">
<a href="/">Qwik Speak ⚡️</a>
</section>
<nav>
<a href={getHref('/')}
class={{ active: pathname.endsWith('/') || config.supportedLocales.some(x => pathname.endsWith(x.lang)) }}>
{t('app.nav.home')}
</a>
<a href={getHref('/page')}
class={{ active: pathname.endsWith('/page') }}>
{t('app.nav.page')}
</a>
</nav>
<ChangeLocale />
</div>
</header>
);
});
16 changes: 12 additions & 4 deletions src/app/routes/[...lang]/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,13 +15,20 @@ export const Home = component$(() => {
<h1>{t('app.title')}</h1>
<h3>{t('app.subtitle')}</h3>

{/* Params */}
<h4>{t('home.params')}</h4>
<p>{t('home.greeting', { name: 'Qwik Speak' })}</p>
{/* Html tags */}

<h4>{t('home.tags')}</h4>
<p dangerouslySetInnerHTML={t('home.text')}></p>
{/* Dates */}

<h4>{t('home.plural')}</h4>
<p>{p(1, 'home.devs')}</p>
<p>{p(2, 'home.devs')}</p>

<h4>{t('home.dates')}</h4>
<p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p>
{/* Numbers */}

<h4>{t('home.numbers')}</h4>
<p>{fn(1000000)}</p>
<p>{fn(1000000, { style: 'currency' })}</p>
<p>{fn(1, { style: 'unit', unit: units['length'] })}</p>
Expand Down
4 changes: 2 additions & 2 deletions src/app/routes/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { Header } from '../components/header/header';

export default component$(() => {
return (
<>
<div>
<Header />
<main>
<Slot />
</main>
</>
</div>
);
});

Expand Down
33 changes: 21 additions & 12 deletions src/global.css
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/library/format-date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/library/format-number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 34 additions & 0 deletions src/library/plural.ts
Original file line number Diff line number Diff line change
@@ -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);
};
2 changes: 1 addition & 1 deletion src/library/qwik-speak.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
4 changes: 3 additions & 1 deletion src/tests/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
8 changes: 4 additions & 4 deletions src/tests/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
13 changes: 13 additions & 0 deletions src/tests/plural.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});

0 comments on commit 80681d5

Please sign in to comment.