Skip to content

Commit

Permalink
feat: 🎸 Support getting deep localized objects/arrays
Browse files Browse the repository at this point in the history
✅ Closes: Closes #83
  • Loading branch information
kaisermann committed Nov 5, 2020
1 parent 5c3191d commit ff54136
Show file tree
Hide file tree
Showing 12 changed files with 6,926 additions and 217 deletions.
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"@babel/preset-env": "^7.11.5",
"@kiwi/eslint-config": "^1.2.0",
"@kiwi/prettier-config": "^1.1.0",
"@types/dlv": "^1.1.2",
"@types/estree": "0.0.45",
"@types/intl": "^1.2.0",
"@types/jest": "^26.0.14",
Expand All @@ -102,6 +103,8 @@
},
"dependencies": {
"commander": "^4.0.1",
"deepmerge": "^4.2.2",
"dlv": "^1.1.3",
"estree-walker": "^0.9.0",
"intl-messageformat": "^7.5.2",
"tiny-glob": "^0.2.6"
Expand Down
4 changes: 2 additions & 2 deletions src/cli/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
import { walk } from 'estree-walker';
import { Ast } from 'svelte/types/compiler/interfaces';
import { parse } from 'svelte/compiler';
import dlv from 'dlv';

import { deepGet } from './includes/deepGet';
import { deepSet } from './includes/deepSet';
import { getObjFromExpression } from './includes/getObjFromExpression';
import { Message } from './types';
Expand Down Expand Up @@ -191,7 +191,7 @@ export function extractMessages(
} else {
if (
overwrite === false &&
typeof deepGet(accumulator, message.meta.id) !== 'undefined'
typeof dlv(accumulator, message.meta.id) !== 'undefined'
) {
return;
}
Expand Down
9 changes: 0 additions & 9 deletions src/cli/includes/deepGet.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/runtime/includes/flatObj.ts

This file was deleted.

29 changes: 14 additions & 15 deletions src/runtime/stores/dictionary.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { writable, derived } from 'svelte/store';
import deepmerge from 'deepmerge';
import dlv from 'dlv';

import { LocaleDictionary, DeepDictionary, Dictionary } from '../types/index';
import { flatObj } from '../includes/flatObj';
import { LocaleDictionary, LocalesDictionary } from '../types/index';
import { getFallbackOf } from './locale';

let dictionary: Dictionary;
const $dictionary = writable<Dictionary>({});
let dictionary: LocalesDictionary;
const $dictionary = writable<LocalesDictionary>({});

export function getLocaleDictionary(locale: string) {
return (dictionary[locale] as LocaleDictionary) || null;
Expand All @@ -20,15 +21,15 @@ export function hasLocaleDictionary(locale: string) {
}

export function getMessageFromDictionary(locale: string, id: string) {
if (hasLocaleDictionary(locale)) {
const localeDictionary = getLocaleDictionary(locale);

if (id in localeDictionary) {
return localeDictionary[id];
}
if (!hasLocaleDictionary(locale)) {
return null;
}

return null;
const localeDictionary = getLocaleDictionary(locale);

const match = dlv(localeDictionary, id);

return match;
}

export function getClosestAvailableLocale(locale: string): string | null {
Expand All @@ -37,11 +38,9 @@ export function getClosestAvailableLocale(locale: string): string | null {
return getClosestAvailableLocale(getFallbackOf(locale));
}

export function addMessages(locale: string, ...partials: DeepDictionary[]) {
const flattedPartials = partials.map((partial) => flatObj(partial));

export function addMessages(locale: string, ...partials: LocaleDictionary[]) {
$dictionary.update((d) => {
d[locale] = Object.assign(d[locale] || {}, ...flattedPartials);
d[locale] = deepmerge.all<LocaleDictionary>([d[locale] || {}, ...partials]);

return d;
});
Expand Down
9 changes: 5 additions & 4 deletions src/runtime/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Formats } from 'intl-messageformat';

export interface DeepDictionary {
[key: string]: DeepDictionary | string | string[];
export interface LocaleDictionary {
[key: string]: LocaleDictionary | LocaleDictionary[] | string | string[];
}
export type LocaleDictionary = Record<string, string>;
export type Dictionary = Record<string, LocaleDictionary>;
export type LocalesDictionary = {
[key: string]: LocaleDictionary;
};

export interface MessageObject {
id?: string;
Expand Down
17 changes: 0 additions & 17 deletions test/cli/includes.test.ts

This file was deleted.

87 changes: 59 additions & 28 deletions test/runtime/includes/lookup.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,82 @@
import { lookup, lookupCache } from '../../../src/runtime/includes/lookup'
import { $dictionary, addMessages } from '../../../src/runtime/stores/dictionary'
import { lookup, lookupCache } from '../../../src/runtime/includes/lookup';
import {
$dictionary,
addMessages,
} from '../../../src/runtime/stores/dictionary';

beforeEach(() => {
$dictionary.set({})
})
$dictionary.set({});
});

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

test('gets a shallow message of a locale dictionary', () => {
addMessages('en', { field: 'name' })
addMessages('en', { field: 'name' });

expect(lookup('field', 'en')).toBe('name')
})
expect(lookup('field', 'en')).toBe('name');
});

test('gets a deep message of a locale dictionary', () => {
addMessages('en', { deep: { field: 'lastname' } })
expect(lookup('deep.field', 'en')).toBe('lastname')
})
addMessages('en', { deep: { field: 'lastname' } });
expect(lookup('deep.field', 'en')).toBe('lastname');
});

test('gets a message from the fallback dictionary', () => {
addMessages('en', { field: 'name' })
addMessages('en', { field: 'name' });

expect(lookup('field', 'en-US')).toBe('name')
})
expect(lookup('field', 'en-US')).toBe('name');
});

test('gets an array ', () => {
addMessages('en', {
careers: [
{
role: 'Role 1',
description: 'Description 1',
},
{
role: 'Role 2',
description: 'Description 2',
},
],
});

expect(lookup('careers', 'en-US')).toMatchInlineSnapshot(`
Array [
Object {
"description": "Description 1",
"role": "Role 1",
},
Object {
"description": "Description 2",
"role": "Role 2",
},
]
`);
});

test('caches found messages by locale', () => {
addMessages('en', { field: 'name' })
addMessages('pt', { field: 'nome' })
lookup('field', 'en-US')
lookup('field', 'pt')
addMessages('en', { field: 'name' });
addMessages('pt', { field: 'nome' });
lookup('field', 'en-US');
lookup('field', 'pt');

expect(lookupCache).toMatchObject({
'en-US': { field: 'name' },
pt: { field: 'nome' },
})
})
});
});

test("doesn't cache falsy messages", () => {
addMessages('en', { field: 'name' })
addMessages('pt', { field: 'nome' })
lookup('field_2', 'en-US')
lookup('field_2', 'pt')
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' },
})
})
});
});
93 changes: 33 additions & 60 deletions test/runtime/includes/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,71 +4,44 @@ import {
getLocaleFromNavigator,
getLocaleFromPathname,
getLocaleFromHostname,
} from '../../../src/runtime/includes/localeGetters'
import { flatObj } from '../../../src/runtime/includes/flatObj'
} from '../../../src/runtime/includes/localeGetters';

describe('getting client locale', () => {
beforeEach(() => {
delete window.location
delete window.location;
window.location = {
pathname: '/',
hostname: 'example.com',
hash: '',
search: '',
} as any
})

test('gets the locale based on the passed hash parameter', () => {
window.location.hash = '#locale=en-US&lang=pt-BR'
expect(getLocaleFromHash('lang')).toBe('pt-BR')
})

test('gets the locale based on the passed search parameter', () => {
window.location.search = '?locale=en-US&lang=pt-BR'
expect(getLocaleFromQueryString('lang')).toBe('pt-BR')
})

test('gets the locale based on the navigator language', () => {
expect(getLocaleFromNavigator()).toBe(window.navigator.language)
})

test('gets the locale based on the pathname', () => {
window.location.pathname = '/en-US/foo/'
expect(getLocaleFromPathname(/^\/(.*?)\//)).toBe('en-US')
})

test('gets the locale base on the hostname', () => {
window.location.hostname = 'pt.example.com'
expect(getLocaleFromHostname(/^(.*?)\./)).toBe('pt')
})

test('returns null if no locale was found', () => {
expect(getLocaleFromQueryString('lang')).toBe(null)
})
})

describe('deep object handling', () => {
test('flattens a deep object', () => {
const obj = {
a: { b: { c: { d: 'foo' } } },
e: { f: 'bar' },
}
expect(flatObj(obj)).toMatchObject({
'a.b.c.d': 'foo',
'e.f': 'bar',
})
})

test('flattens a deep object with array values', () => {
const obj = {
a: { b: { c: { d: ['foo', 'bar'] } } },
e: { f: ['foo', 'bar'] },
}
expect(flatObj(obj)).toMatchObject({
'a.b.c.d.0': 'foo',
'a.b.c.d.1': 'bar',
'e.f.0': 'foo',
'e.f.1': 'bar',
})
})
})
} as any;
});

it('gets the locale based on the passed hash parameter', () => {
window.location.hash = '#locale=en-US&lang=pt-BR';
expect(getLocaleFromHash('lang')).toBe('pt-BR');
});

it('gets the locale based on the passed search parameter', () => {
window.location.search = '?locale=en-US&lang=pt-BR';
expect(getLocaleFromQueryString('lang')).toBe('pt-BR');
});

it('gets the locale based on the navigator language', () => {
expect(getLocaleFromNavigator()).toBe(window.navigator.language);
});

it('gets the locale based on the pathname', () => {
window.location.pathname = '/en-US/foo/';
expect(getLocaleFromPathname(/^\/(.*?)\//)).toBe('en-US');
});

it('gets the locale base on the hostname', () => {
window.location.hostname = 'pt.example.com';
expect(getLocaleFromHostname(/^(.*?)\./)).toBe('pt');
});

it('returns null if no locale was found', () => {
expect(getLocaleFromQueryString('lang')).toBeNull();
});
});
Loading

0 comments on commit ff54136

Please sign in to comment.