Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🎸 add $json method to get raw dictionary values #110

Merged
merged 5 commits into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# [3.3.0](https://github.com/kaisermann/svelte-i18n/compare/v3.2.7...v3.3.0) (2020-11-24)


### Features

* 🎸 add $json method to get raw dictionary values ([52400b5](https://github.com/kaisermann/svelte-i18n/commit/52400b5c51213b45270da101aab6e8ae2bda024c)), closes [#109](https://github.com/kaisermann/svelte-i18n/issues/109) [#83](https://github.com/kaisermann/svelte-i18n/issues/83)



## [3.2.7](https://github.com/kaisermann/svelte-i18n/compare/v3.2.6...v3.2.7) (2020-11-23)


Expand Down
54 changes: 40 additions & 14 deletions docs/Formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
<!-- code_chunk_output -->

- [Message syntax](#message-syntax)
- [`$format` or `$_` or `$t`](#format-or-_-or-t)
- [`$format`, `$_` or `$t`](#format-_-or-t)
- [`$time(number: Date, options: MessageObject)`](#timenumber-date-options-messageobject)
- [`$date(date: Date, options: MessageObject)`](#datedate-date-options-messageobject)
- [`$number(number: number, options: MessageObject)`](#numbernumber-number-options-messageobject)
- [`$json(messageId: string)`](#jsonmessageid-string)
- [Formats](#formats)
- [Accessing formatters directly](#accessing-formatters-directly)

Expand All @@ -20,7 +21,7 @@ Under the hood, `formatjs` is used for localizing your messages. It allows `svel
- [Runtime Environments](https://formatjs.io/docs/guides/runtime-requirements/)
- [ICU Message Syntax](https://formatjs.io/docs/core-concepts/icu-syntax/)

### `$format` or `$_` or `$t`
### `$format`, `$_` or `$t`

`import { _, t, format } from 'svelte-i18n'`

Expand All @@ -41,11 +42,11 @@ The formatter can be called with two different signatures:

```ts
interface MessageObject {
id?: string
locale?: string
format?: string
default?: string
values?: Record<string, string | number | Date>
id?: string;
locale?: string;
format?: string;
default?: string;
values?: Record<string, string | number | Date>;
}
```

Expand All @@ -56,6 +57,7 @@ interface MessageObject {
- `values`: properties that should be interpolated in the message;

You can pass a `string` as the first parameter for a less verbose way of formatting a message. It is also possible to inject values into the translation like so:

```jsonc
// en.json
{
Expand Down Expand Up @@ -126,6 +128,30 @@ Formats a number with the specified locale and format. Please refer to the [#for
<!-- 100.000.000 -->
```

### `$json(messageId: string)`

`import { json } from 'svelte-i18n'`

Returns the raw JSON value of the specified `messageId` for the current locale. While [`$format`](#format-_-or-t) always returns a string, `$json` can be used to get an object relative to the current locale.

```html
<ul>
{#each $json('list.items') as item}
<li>{item.name}</li>
{/each}
</ul>
```

If you're using TypeScript, you can define the returned type as well:

```html
<ul>
{#each $json<Item[]>('list.items') as item}
<li>{item.name}</li>
{/each}
</ul>
```

### Formats

`svelte-i18n` comes with a default set of `number`, `time` and `date` formats:
Expand Down Expand Up @@ -163,24 +189,24 @@ import {
getNumberFormatter,
getTimeFormatter,
getMessageFormatter,
} from 'svelte-i18n'
} from 'svelte-i18n';
```

By using these methods, it's possible to manipulate values in a more specific way that fits your needs. For example, it's possible to create a method which receives a `date` and returns its relevant date related parts:

```js
import { getDateFormatter } from 'svelte-i18n'
import { getDateFormatter } from 'svelte-i18n';

const getDateParts = date =>
const getDateParts = (date) =>
getDateFormatter()
.formatToParts(date)
.filter(({ type }) => type !== 'literal')
.reduce((acc, { type, value }) => {
acc[type] = value
return acc
}, {})
acc[type] = value;
return acc;
}, {});

getDateParts(new Date(2020, 0, 1)) // { month: '1', day: '1', year: '2020' }
getDateParts(new Date(2020, 0, 1)); // { month: '1', day: '1', year: '2020' }
```

Check the [methods documentation](/docs/Methods.md#low-level-api) for more information.
2 changes: 1 addition & 1 deletion docs/Getting Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,4 @@ After having the initial locale set, you're ready to start localizing your app.
</nav>
```

See [Formatting](/docs/Formatting.md) to read about the supported message syntax.
See [Formatting](/docs/Formatting.md) to read about the supported message syntax and all the available formatters.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "svelte-i18n",
"version": "3.2.7",
"version": "3.3.0",
"main": "dist/runtime.cjs.js",
"module": "dist/runtime.esm.js",
"types": "types/runtime/index.d.ts",
Expand Down
12 changes: 8 additions & 4 deletions src/runtime/includes/lookup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { getMessageFromDictionary } from '../stores/dictionary';
import { getFallbackOf } from '../stores/locale';

export const lookupCache: Record<string, Record<string, string>> = {};
export const lookupCache: {
[locale: string]: {
[messageId: string]: any;
};
} = {};

const addToCache = (path: string, locale: string, message: string) => {
if (!message) return message;
Expand All @@ -11,8 +15,8 @@ const addToCache = (path: string, locale: string, message: string) => {
return message;
};

const searchForMessage = (path: string, locale: string): string => {
if (locale == null) return null;
const searchForMessage = (path: string, locale: string): any => {
if (locale == null) return undefined;

const message = getMessageFromDictionary(locale, path);

Expand All @@ -32,5 +36,5 @@ export const lookup = (path: string, locale: string) => {
return addToCache(path, locale, message);
}

return null;
return undefined;
};
68 changes: 42 additions & 26 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import { MessageObject } from './types';
import { getCurrentLocale } from './stores/locale';
import { getOptions } from './configs';
import { flush } from './includes/loaderQueue';
import { getCurrentLocale, $locale } from './stores/locale';
import { getOptions, init } from './configs';
import { flush, registerLocaleLoader } from './includes/loaderQueue';
import {
getLocaleFromHostname,
getLocaleFromPathname,
getLocaleFromNavigator,
getLocaleFromQueryString,
getLocaleFromHash,
} from './includes/localeGetters';
import { $dictionary, $locales, addMessages } from './stores/dictionary';
import { $isLoading } from './stores/loading';
import {
$format,
$formatDate,
$formatNumber,
$formatTime,
$getJSON,
} from './stores/formatters';
import {
getDateFormatter,
getNumberFormatter,
getTimeFormatter,
getMessageFormatter,
} from './includes/formatters';

// defineMessages allow us to define and extract dynamic message ids
export function defineMessages(i: Record<string, MessageObject>) {
Expand All @@ -12,39 +34,33 @@ export function waitLocale(locale?: string) {
return flush(locale || getCurrentLocale() || getOptions().initialLocale);
}

export { init } from './configs';
export {
getLocaleFromHostname,
getLocaleFromPathname,
getLocaleFromNavigator,
getLocaleFromQueryString,
getLocaleFromHash,
} from './includes/localeGetters';

export { $locale as locale } from './stores/locale';

export {
// setup
init,
addMessages,
registerLocaleLoader as register,
// stores
$locale as locale,
$dictionary as dictionary,
$locales as locales,
addMessages,
} from './stores/dictionary';
export { registerLocaleLoader as register } from './includes/loaderQueue';

export { $isLoading as isLoading } from './stores/loading';

export {
$isLoading as isLoading,
// reactive methods
$format as format,
$format as _,
$format as t,
$formatDate as date,
$formatNumber as number,
$formatTime as time,
} from './stores/formatters';

// low-level
export {
$getJSON as json,
// low-level
getDateFormatter,
getNumberFormatter,
getTimeFormatter,
getMessageFormatter,
} from './includes/formatters';
// utils
getLocaleFromHostname,
getLocaleFromPathname,
getLocaleFromNavigator,
getLocaleFromQueryString,
getLocaleFromHash,
};
31 changes: 24 additions & 7 deletions src/runtime/stores/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
TimeFormatter,
DateFormatter,
NumberFormatter,
JSONGetter,
} from '../types';
import { lookup } from '../includes/lookup';
import { hasLocaleQueue } from '../includes/loaderQueue';
Expand Down Expand Up @@ -54,23 +55,39 @@ const formatMessage: MessageFormatter = (id, options = {}) => {
}

message = defaultValue || id;
} else if (typeof message !== 'string') {
console.warn(
`[svelte-i18n] Message with id "${id}" must be of type "string", found: "${typeof message}". Gettin its value through the "$format" method is deprecated; use the "json" method instead.`,
);

return message;
}

if (!values) return message;
if (!values) {
return message;
}

return getMessageFormatter(message, locale).format(values) as string;
};

const formatTime: TimeFormatter = (t, options) =>
getTimeFormatter(options).format(t);
const formatTime: TimeFormatter = (t, options) => {
return getTimeFormatter(options).format(t);
};

const formatDate: DateFormatter = (d, options) => {
return getDateFormatter(options).format(d);
};

const formatDate: DateFormatter = (d, options) =>
getDateFormatter(options).format(d);
const formatNumber: NumberFormatter = (n, options) => {
return getNumberFormatter(options).format(n);
};

const formatNumber: NumberFormatter = (n, options) =>
getNumberFormatter(options).format(n);
const getJSON: JSONGetter = <T>(id: string, locale = getCurrentLocale()) => {
return lookup(id, locale) as T;
};

export const $format = derived([$locale, $dictionary], () => formatMessage);
export const $formatTime = derived([$locale], () => formatTime);
export const $formatDate = derived([$locale], () => formatDate);
export const $formatNumber = derived([$locale], () => formatNumber);
export const $getJSON = derived([$locale, $dictionary], () => getJSON);
2 changes: 2 additions & 0 deletions src/runtime/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export type NumberFormatter = (
options?: IntlFormatterOptions<Intl.NumberFormatOptions>,
) => string;

export type JSONGetter = <T extends any>(id: string, locale?: string) => T;

type IntlFormatterOptions<T> = T & {
format?: string;
locale?: string;
Expand Down
7 changes: 5 additions & 2 deletions test/runtime/includes/lookup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ beforeEach(() => {
});

test('returns null if no locale was passed', () => {
expect(lookup('message.id', undefined)).toBeNull();
expect(lookup('message.id', null)).toBeNull();
expect(lookup('message.id', undefined)).toBeUndefined();
expect(lookup('message.id', null)).toBeUndefined();
});

test('gets a shallow message of a locale dictionary', () => {
Expand Down Expand Up @@ -61,6 +61,7 @@ test('gets an array ', () => {
test('caches found messages by locale', () => {
addMessages('en', { field: 'name' });
addMessages('pt', { field: 'nome' });

lookup('field', 'en-US');
lookup('field', 'pt');

Expand All @@ -73,8 +74,10 @@ test('caches found messages by locale', () => {
test("doesn't cache falsy messages", () => {
addMessages('en', { field: 'name' });
addMessages('pt', { field: 'nome' });

lookup('field_2', 'en-US');
lookup('field_2', 'pt');

expect(lookupCache).not.toMatchObject({
'en-US': { field_2: 'name' },
pt: { field_2: 'nome' },
Expand Down
Loading