Skip to content

WASM Demo #4684

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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion docs/tutorials/js.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ _We are still working on improving the user experience of using ICU4X from other

Similar to C++, the JS APIs mirror the Rust code in the `icu_capi` crate, which can be explored on [docs.rs][rust-docs], though the precise types used may be different.

See [`ffi/npm/examples/wasm-demo`] for an NPM package that uses the ICU4X package. You can run it using `npm run start`.
See [`docs/tutorials/npm`] for an NPM package that uses the ICU4X package. You can run it using `npm run start`.

We hope to fill in these docs over time with more examples.

Expand Down
137 changes: 137 additions & 0 deletions docs/tutorials/npm/gen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/bin/bash

locales=("ja" "th" "zh" "bn" "und" "de" "en")

for locale in "${locales[@]}"; do
icu4x-datagen --keys \
"datetime/symbols/hebrew/years@1" \
"segmenter/dictionary/w_auto@1" \
"datetime/symbols/weekdays@1" \
"datetime/symbols/dangi/months@1" \
"time_zone/iana_to_bcp47@1" \
"datetime/skeletons@1" \
"time_zone/bcp47_to_iana@1" \
"datetime/patterns/chinese/date@1" \
"datetime/roc/datesymbols@1" \
"datetime/patterns/coptic/date@1" \
"datetime/symbols/persian/months@1" \
"datetime/hebrew/datelengths@1" \
"decimal/symbols@1" \
"datetime/symbols/islamic/years@1" \
"datetime/symbols/indian/months@1" \
"locid_transform/script_dir@1" \
"fallback/supplement/co@1" \
"datetime/symbols/buddhist/months@1" \
"datetime/patterns/ethiopic/date@1" \
"plurals/ranges@1" \
"segmenter/grapheme@1" \
"time_zone/metazone_period@1" \
"datetime/persian/datelengths@1" \
"datetime/ethiopic/datesymbols@1" \
"time_zone/generic_short@1" \
"datetime/symbols/japanese/months@1" \
"time_zone/generic_long@1" \
"normalizer/nfkd@1" \
"datetime/patterns/persian/date@1" \
"datetime/symbols/ethiopic/months@1" \
"datetime/patterns/buddhist/date@1" \
"locid_transform/likelysubtags_l@1" \
"datetime/symbols/persian/years@1" \
"datetime/patterns/hebrew/date@1" \
"datetime/symbols/buddhist/years@1" \
"datetime/chinese/datelengths@1" \
"datetime/symbols/japanext/months@1" \
"plurals/ordinal@1" \
"datetime/symbols/coptic/years@1" \
"datetime/patterns/islamic/date@1" \
"datetime/patterns/time@1" \
"datetime/symbols/dangi/years@1" \
"time_zone/formats@1" \
"datetime/patterns/dangi/date@1" \
"datetime/japanese/datelengths@1" \
"datetime/ethiopic/datelengths@1" \
"plurals/cardinal@1" \
"datetime/indian/datelengths@1" \
"segmenter/lstm/wl_auto@1" \
"datetime/symbols/chinese/years@1" \
"fallback/parents@1" \
"datetime/gregory/datelengths@1" \
"datetime/patterns/gregory/date@1" \
"datetime/symbols/japanext/years@1" \
"datetime/gregory/datesymbols@1" \
"datetime/week_data@1" \
"time_zone/exemplar_cities@1" \
"datetime/coptic/datelengths@1" \
"segmenter/dictionary/wl_ext@1" \
"list/or@1" \
"datetime/dangi/datelengths@1" \
"datetime/persian/datesymbols@1" \
"datetime/buddhist/datelengths@1" \
"normalizer/nfd@1" \
"datetime/patterns/roc/date@1" \
"time_zone/specific_short@1" \
"datetime/japanese/datesymbols@1" \
"segmenter/line@1" \
"datetime/symbols/hebrew/months@1" \
"datetime/buddhist/datesymbols@1" \
"datetime/symbols/ethiopic/years@1" \
"datetime/coptic/datesymbols@1" \
"normalizer/nfkdex@1" \
"time_zone/specific_long@1" \
"datetime/roc/datelengths@1" \
"calendar/japanext@1" \
"datetime/japanext/datesymbols@1" \
"normalizer/decomp@1" \
"datetime/islamic/datelengths@1" \
"list/unit@1" \
"datetime/timelengths@1" \
"segmenter/word@1" \
"datetime/dangi/datesymbols@1" \
"datetime/islamic/datesymbols@1" \
"datetime/symbols/gregory/months@1" \
"datetime/timesymbols@1" \
"datetime/symbols/japanese/years@1" \
"datetime/patterns/japanese/date@1" \
"datetime/indian/datesymbols@1" \
"datetime/patterns/japanext/date@1" \
"datetime/patterns/datetime@1" \
"segmenter/sentence@1" \
"datetime/symbols/chinese/months@1" \
"locid_transform/aliases@1" \
"datetime/symbols/roc/years@1" \
"locid_transform/likelysubtags_sr@1" \
"datetime/symbols/indian/years@1" \
"datetime/chinese/datesymbols@1" \
"normalizer/uts46d@1" \
"locid_transform/likelysubtags@1" \
"locid_transform/likelysubtags_ext@1" \
"datetime/symbols/gregory/years@1" \
"datetime/japanext/datelengths@1" \
"datetime/symbols/coptic/months@1" \
"datetime/hebrew/datesymbols@1" \
"list/and@1" \
"normalizer/comp@1" \
"fallback/likelysubtags@1" \
"datetime/patterns/indian/date@1" \
"calendar/japanese@1" \
"datetime/symbols/islamic/months@1" \
"datetime/symbols/roc/months@1" \
"datetime/symbols/dayperiods@1" \
"normalizer/nfdex@1" \
--fallback preresolved --locales $locale --format blob2 --out dist/$locale.postcard --overwrite
done

ts_content="const locales: string[] = ["

for locale in "${locales[@]}"; do
ts_content+="\"$locale\", "
done

ts_content=${ts_content%, }
ts_content+="];"

ts_content+="\nexport default locales;"

echo "$ts_content" > dist/locales.ts

echo "locales.ts file has been generated."
2 changes: 1 addition & 1 deletion docs/tutorials/npm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"clean": "rm dist/*",
"build": "webpack",
"build": "sh gen.sh && webpack ",
"start": "webpack serve --mode development --port 12349",
"tsc": "tsc -p ."
},
Expand Down
12 changes: 7 additions & 5 deletions docs/tutorials/npm/src/ts/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ICU4XDataProvider } from 'icu4x';
import { DataProviderManager } from './data-provider-manager';
import * as fdf from './fixed-decimal';
import * as dtf from './date-time';
import * as seg from './segmenter';
Expand All @@ -8,9 +8,11 @@ import 'bootstrap/js/dist/dropdown';
import 'bootstrap/js/dist/collapse';

(async function init() {
const dataProvider = ICU4XDataProvider.create_compiled();
fdf.setup(dataProvider);
dtf.setup(dataProvider);
seg.setup(dataProvider);
const dataManager = await DataProviderManager.create();

fdf.setup(dataManager);
dtf.setup(dataManager);
seg.setup(dataManager);

(document.querySelector("#bigspinner") as HTMLElement).style.display = "none";
})()
124 changes: 124 additions & 0 deletions docs/tutorials/npm/src/ts/data-provider-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
ICU4XDataProvider,
ICU4XLocale,
ICU4XLocaleFallbacker,
ICU4XLocaleFallbackConfig,
ICU4XLocaleFallbackPriority
} from 'icu4x';
import * as localeDefault from '../../dist/locales';

export class DataProviderManager {

private dataProvider: ICU4XDataProvider;
private fallbacker: ICU4XLocaleFallbacker;
private fallbackLocale: ICU4XLocale;
private loadedLocales: Set <ICU4XLocale> ;

private constructor() {
this.loadedLocales = new Set < ICU4XLocale > ();
}

public static async create(): Promise <DataProviderManager> {
const manager = new DataProviderManager();
await manager.init();
return manager;
}

private async init() {

const enFilePath = 'dist/en.postcard';
let enProvider = await this.createDataProviderFromBlob(enFilePath);
this.loadedLocales.add(ICU4XLocale.create_from_string("en"));

const unFilePath = 'dist/en.postcard';
let unProvider = await this.createDataProviderFromBlob(unFilePath);


let fallbacker = ICU4XLocaleFallbacker.create(unProvider);
let fallbackConfig = new ICU4XLocaleFallbackConfig();
fallbackConfig.priority = ICU4XLocaleFallbackPriority.Language;
let fallbackerWithConfig = fallbacker.for_config(fallbackConfig);

this.fallbacker = fallbackerWithConfig;
enProvider.enable_locale_fallback_with(this.fallbacker);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: enable_locale_fallback_with should take a ICU4XLocaleFallbacker, not a ICU4XLocaleFallbackerWithConfig. I think TypeScript should catch this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I wanted to display the fallbacked locale. ICU4XLocaleFallbackerWithConfig gives an iterator which helps to get the fallback, not sure how to achieve this using ICU4XLocaleFallbacker.

Also it doesn't through an error 😕

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolved locale is returned in DataResponseMetdata.locale (None if it's the request locale).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(oh this is not Rust, ignore my comment).

this.dataProvider = enProvider;
}

private async createDataProviderFromBlob(filePath: string): Promise < ICU4XDataProvider > {
const blob = await this.readBlobFromFile(filePath);
const arrayBuffer = await blob.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const newDataProvider = ICU4XDataProvider.create_from_byte_slice(uint8Array);
return newDataProvider;
}

private async readBlobFromFile(path: string): Promise < Blob > {
const response = await fetch(path);
if(!response.ok) {
throw new Error(`Failed to fetch file: ${response.statusText}`);
}
const blob = await response.blob();
return blob;
}

public async trackLocaleFallback(fallbacker: ICU4XLocaleFallbacker, locale: ICU4XLocale): Promise < string > {
let localeSet = new Set(localeDefault.default);
const initialLocale = locale.to_string();
let fallbackIterator = fallbacker.fallback_for_locale(locale);
while(fallbackIterator.get().to_string() != 'und') {
const fallbackLocale = fallbackIterator.get().to_string();
if(initialLocale != fallbackLocale){
console.log(`Tracking back to: ${fallbackLocale}`);
}
if(localeSet.has(fallbackLocale)) {
return fallbackLocale;
}
fallbackIterator.step();
}
return 'und';
}

public async loadLocale(newLocale: string): Promise < ICU4XDataProvider > {
const icu4xLocale = ICU4XLocale.create_from_string(newLocale);
const fallbackLocale = await this.trackLocaleFallback(this.fallbacker, icu4xLocale);
const newFilePath = `dist/${fallbackLocale}.postcard`;
let newProvider = await this.createDataProviderFromBlob(newFilePath);
await newProvider.fork_by_locale(this.dataProvider);
this.dataProvider = newProvider;
this.loadedLocales.add(ICU4XLocale.create_from_string(fallbackLocale));
this.fallbackLocale = ICU4XLocale.create_from_string(fallbackLocale);
return newProvider;
}

public async getSegmenterProviderLocale () : Promise <ICU4XDataProvider> {
const segmenterLocale = ['ja', 'zh', 'th'];
let segmenterProvider: ICU4XDataProvider;
for(let i = 0 ; i < segmenterLocale.length ; i++){
segmenterProvider = await this.loadLocale(segmenterLocale[i]);
}
return segmenterProvider;
}

public async getDeDataProvider() {
const newFilePath = `dist/de.postcard`;
let newProvider = await this.createDataProviderFromBlob(newFilePath);
return newProvider;
}

public getLoadedLocales() {
return this.loadedLocales;
}

public getDataProvider(): ICU4XDataProvider {
return this.dataProvider;
}

public getFallbacker(): ICU4XLocaleFallbacker {
return this.fallbacker;
}

public getFallbackLocale(): ICU4XLocale {
return this.fallbackLocale;
}

}
56 changes: 47 additions & 9 deletions docs/tutorials/npm/src/ts/date-time.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { ICU4XDataProvider, ICU4XDateLength, ICU4XDateTime, ICU4XDateTimeFormatter, ICU4XLocale, ICU4XTimeLength, ICU4XCalendar } from "icu4x";
import { Ok, Result, result, unwrap } from "./index";
import {
ICU4XDataProvider,
ICU4XDateLength,
ICU4XDateTime,
ICU4XDateTimeFormatter,
ICU4XLocale,
ICU4XTimeLength,
ICU4XCalendar
} from "icu4x";
import { DataProviderManager } from './data-provider-manager';
import {
Ok,
Result,
result,
unwrap
} from "./index";

export class DateTimeDemo {
#displayFn: (formatted: string) => void;
#dataProvider: ICU4XDataProvider;
#dataProviderManager: DataProviderManager;

#localeStr: string;
#calendarStr: string;
Expand All @@ -16,12 +31,13 @@ export class DateTimeDemo {
#formatter: Result<ICU4XDateTimeFormatter>;
#dateTime: Result<ICU4XDateTime> | null;

constructor(displayFn: (formatted: string) => void, dataProvider: ICU4XDataProvider) {
constructor(displayFn: (formatted: string) => void, dataProviderManager: DataProviderManager) {

this.#displayFn = displayFn;
this.#dataProvider = dataProvider;

this.#dataProvider = dataProviderManager.getDataProvider();
this.#dataProviderManager = dataProviderManager;
this.#locale = Ok(ICU4XLocale.create_from_string("en"));
this.#calendar = Ok(ICU4XCalendar.create_for_locale(dataProvider, unwrap(this.#locale)));
this.#calendar = Ok(ICU4XCalendar.create_for_locale(this.#dataProvider, unwrap(this.#locale)));
this.#dateLength = ICU4XDateLength.Short;
this.#timeLength = ICU4XTimeLength.Short;
this.#dateTime = null;
Expand All @@ -37,9 +53,31 @@ export class DateTimeDemo {
this.#updateFormatter();
}

setLocale(locid: string): void {
this.#localeStr = locid;
this.#updateLocaleAndCalendar();
async setLocale(locid: string): Promise <void> {
this.#locale = result(() => ICU4XLocale.create_from_string(locid));
if (this.#locale.ok == true) {
const locales = this.#dataProviderManager.getLoadedLocales();
const localesFinal: string[] = [];
locales.forEach((item: ICU4XLocale) => {
localesFinal.push(item.to_string)
})
const loadedLocales = new Set(localesFinal);
if (!loadedLocales.has(this.#locale.value.to_string())) {
await this.updateProvider(this.#locale.value);
}
}
this.#updateFormatter()
}

async updateProvider(newLocale: ICU4XLocale) {
this.#dataProvider = await this.#dataProviderManager.loadLocale(newLocale.to_string());
const fallbackLocale = this.#dataProviderManager.getFallbackLocale();
const fallbackLocaleResult = result(() => this.#dataProviderManager.getFallbackLocale());
this.#locale = fallbackLocaleResult
// Logs if there is a fallback and prints the fallback locale
if(newLocale.to_string() != fallbackLocale.to_string()) {
console.log(`Falling back to locale: ${fallbackLocale.to_string()}`);
}
this.#updateFormatter();
}

Expand Down
Loading