Skip to content

Commit

Permalink
feat: Add PluralRules locale data shimming (#200)
Browse files Browse the repository at this point in the history
* Remove @formatjs/intl-pluralrule

* Add custom PluralRules

* Add more tests; Fix supportedLocalesOf and getCanonicalLocales

* Add localize test

* Fix lint

* Cleanup

* Remove redundant getCanonicalLocales call
  • Loading branch information
bearfriend authored Sep 27, 2024
1 parent 2b8d554 commit e6f11dd
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 5 deletions.
1 change: 0 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ updates:
# update-package-lock workflow already handles minor/patch updates
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
- dependency-name: "@formatjs/intl-pluralrules"
78 changes: 78 additions & 0 deletions lib/PluralRules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const localeData = {
mi: {
aliases: ['mri', 'mao'],
options: {
cardinal: {
locale: 'mi',
pluralCategories : [ 'one', 'other' ],
shim: true
},
ordinal: {
locale: 'mi',
pluralCategories : [ 'other' ],
shim: true
},
},
select(n, ord) {
return !ord && n === 1 ? 'one' : 'other';
}
}
};

function getCanonicalLocales(locales) {
const mappedLocales = [locales].flat().map(locale => {
for (const canonicalLocale in localeData) {
if (localeData[canonicalLocale].aliases.includes(locale)) {
return canonicalLocale;
}
}
return locale;
});
return Intl.getCanonicalLocales(mappedLocales);
}

class PluralRules extends Intl.PluralRules {

static shim = true;
static supportedLocalesOf(locales) {
return [locales].flat().map(l => {
const canonicalLocale = getCanonicalLocales(l)[0];
if (localeData[canonicalLocale]) {
return canonicalLocale;
}
return super.supportedLocalesOf(l);
}).flat();
}
#localeData;
#locale;
#type;

constructor(locales, options = {}) {
super(locales, options);
this.#locale = PluralRules.supportedLocalesOf(locales)[0];
this.#type = options.type ?? 'cardinal';
if (localeData[this.#locale]) {
this.#localeData = localeData[this.#locale];
}
}

resolvedOptions() {
return { ...super.resolvedOptions(), ...this.#localeData?.options[this.#type] };
}

select(n) {
if (this.#localeData) {
return this.#localeData.select(n, this.#type === 'ordinal');
} else {
return super.select(n);
}
}

}

Object.defineProperty(Intl, 'PluralRules', {
value: PluralRules,
writable: true,
enumerable: false,
configurable: true,
});
4 changes: 2 additions & 2 deletions lib/localize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import '@formatjs/intl-pluralrules/dist-es6/polyfill-locales.js';
import { defaultLocale as fallbackLang, getDocumentLocaleSettings, supportedLangpacks } from '../lib/common.js';
import './PluralRules.js';
import { defaultLocale as fallbackLang, getDocumentLocaleSettings, supportedLangpacks } from './common.js';
import { getLocalizeOverrideResources } from '../helpers/getLocalizeResources.js';
import IntlMessageFormat from 'intl-messageformat';

Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"sinon": "^19.0.2"
},
"dependencies": {
"@formatjs/intl-pluralrules": "^1",
"intl-messageformat": "^10"
}
}
79 changes: 79 additions & 0 deletions test/PluralRules.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import '../lib/PluralRules.js';
import { expect } from '@brightspace-ui/testing';
import { getDocumentLocaleSettings } from '../lib/common.js';

describe('PluralRules', () => {

const documentLocaleSettings = getDocumentLocaleSettings();

afterEach(() => documentLocaleSettings.reset());

it('extends native Intl.PluralRules', () => {
const native = Object.getPrototypeOf(Intl.PluralRules);
expect(Intl.PluralRules.shim).to.be.true;
expect(native).to.have.property('name', 'PluralRules');
expect(native).to.not.have.property('shim');
});

it('uses native data by default', () => {
const shim = new Intl.PluralRules('cy');
const native = new (Object.getPrototypeOf(Intl.PluralRules))('cy');
expect(shim.resolvedOptions()).to.deep.equal(native.resolvedOptions());
expect(shim.select(2)).to.equal('two');
});

it('resolves to canonical locales', () => {
expect(new Intl.PluralRules('mao').resolvedOptions().locale).to.equal('mi');
expect(new Intl.PluralRules('mri').resolvedOptions().locale).to.equal('mi');
expect(new Intl.PluralRules(['abcdefg', 'mri']).resolvedOptions().locale).to.equal('mi');
});

it('includes custom locales as supported', () => {
expect(Intl.PluralRules.supportedLocalesOf(['abc', 'mao', 'en'])).to.deep.equal(['mi', 'en']);
});

[
{
locale: 'mi',
type: 'cardinal',
options: {
locale: 'mi',
shim: true,
pluralCategories: [ 'one', 'other' ]
},
select: {
one: [1],
other: [0, 2, 3, 11]
}
},
{
locale: 'mi',
type: 'ordinal',
options: {
locale: 'mi',
shim: true,
pluralCategories: [ 'other' ]
},
select: {
other: [0, 1, 2, 3, 11]
}
}
].forEach(({ locale, type, options, select }) => {

documentLocaleSettings.language = locale;
const pluralRules = new Intl.PluralRules(locale, { type });

it(`should use custom ${type} data for "${locale}"`, () => {
expect(pluralRules.resolvedOptions()).to.deep.include(options);
});

it(`should select the correct ${type} number categories for "${locale}"`, () => {
options.pluralCategories.forEach(cat => {
select[cat].forEach(num => {
expect(pluralRules.select(num)).to.equal(cat);
});
});
});
});

});
22 changes: 22 additions & 0 deletions test/localize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const resources = {
},
'en-gb': {
basic: '{employerName} is my employer, but British!'
},
mi: {
plural: '{a, plural, one {one} other {other}}',
ordinal: '{a, selectordinal, one {one} other {other}}'
}
};

Expand Down Expand Up @@ -79,6 +83,24 @@ describe('Localize', () => {
expect(localized).to.equal('This message has 2 arguments');
});

it('should select the correct category for shimmed locales', async() => {
await localizer.ready;
document.documentElement.lang = 'mi';
await updatePromise;

const pluralOne = localizer.localize('plural', { a: 1 });
expect(pluralOne).to.equal('one');

const pluralTwo = localizer.localize('plural', { a: 2 });
expect(pluralTwo).to.equal('other');

const ordinalOne = localizer.localize('ordinal', { a: 1 });
expect(ordinalOne).to.equal('other');

const ordinalTwo = localizer.localize('ordinal', { a: 2 });
expect(ordinalTwo).to.equal('other');
});

});

describe('localizeHTML()', () => {
Expand Down

0 comments on commit e6f11dd

Please sign in to comment.