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(framework): allow using a custom i18n library #4119

Merged
merged 8 commits into from
Oct 21, 2021

Conversation

vladitasev
Copy link
Contributor

@vladitasev vladitasev commented Oct 15, 2021

Allow using a custom i18n library

This feature allows to plug in a custom i18n library and use it for all components transparently

Background

All components use 2 APIs for the purpose of i18n:

  • async getI18nBundle to make the request (during the define phase) and get the bundle instance
  • getText - to get the texts (the only method of the instance, acquired in the previous step)

The method above is imported from the following module:

import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";

Proposal

Allow using a third-party i18n library by providing a breakout for async getI18nBundle:

import { registerCustomGetI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
registerCustomGetI18nBundle({
    // custom implementation
});

If the app registers a custom implementation,it will completely replace the stock one (see the change in i18nBundle.js).
The only requirement is that the get function must return an object with a getText method with the expected signature, because this is what all components will be calling.

See the example in : packages/main/customI18n.js for a full replacement implementation. If this module is imported in the bundle, all texts for all components become what is defined by the mock implementation of getText.

@alex-zhang
Copy link

@vladitasev thanks for your quick action after we have a discussion. I will also verify it in my local environment soon.

@alex-zhang
Copy link

alex-zhang commented Oct 17, 2021

@vladitasev looks good for me.

one quick question.

for the function of getText(textObj, ...params) in ui5-webcomponent which support below case:

interface MessageBundle {
  getText(textObj: string | {key: string, defaultText?: string}, ...params: (string|number)[])
}

Maybe there is little different with @xweb/i18n

interface MessageBundle {
  getText(key: string, values?: FormatTextValue | FormatTextValue[] | null, defaultText?: string): string;
}

I think the @xweb/i18n will keep the compatible with the ui5-webcomponent as well.

@alex-zhang
Copy link

I also check the getText in ui5-webcomponent

const itemPositionText = this.i18nBundle.getText(LIST_ITEM_POSITION, [indexOfItem + 1], [this._filteredItems.length]);

I think the getText("someKey", [], []) is incorrect here right? i'm not sure of that.

@vladitasev
Copy link
Contributor Author

vladitasev commented Oct 18, 2021

@alex-zhang Yes, I also noticed that the API of getText is slightly different from gets API not long ago. I have changed the sample code to reflect this, please have a look at it again.

As to your other question: yes, the usage is wrong. The correct usage is getText(MY_KEY, 3, 10) -> "Item 3 of 10 selected". The reason it works with getText(MY_KEY, [3], [10]) too, is coincidental, because of a string conversion, and String([3]) evaluates to '3' just as String(3) does. I have created a separate PR to fix all the wrong usages in all components: #4123.

@alex-zhang
Copy link

@vladitasev hi,

I‘m not sure this is reasonable, maybe we can discuss about it.

const getI18nBundle = packageName => {
    if (I18nBundleInstances.has(packageName)) {
        return I18nBundleInstances.get(packageName);
    }
    
    if (customFetchI18nBundle) {
        const customI18nBundle = customFetchI18nBundle(packageName);
        I18nBundleInstances.set(packageName, customI18nBundle);
        return customI18nBundle;
    }

    const i18nBundle = new I18nBundle(packageName);
    I18nBundleInstances.set(packageName, i18nBundle);
    return i18nBundle;
}

Because if we call registerCustomI18nHandlers and the customFetchI18nBundle will call every time when we call getI18nBundle, I think it will good for performance.

@vladitasev
Copy link
Contributor Author

Hi @alex-zhang

First of all, while working on this topic, we decided to simplify our own i18n implementation by merging async fetchI18nBundle and getI18nBundle into a single async getI18nBundle function. All components have been changed to use this new simpler API.

To reflect this, I have also updated this PR.

As to your comment, yes, the custom function will be called very often, and it is expected to cache the result and not make a network request for the same thing every time. I have updated the sample implementation to reflect this:

let bundle;
registerCustomGetI18nBundle(async packageName => {
	if (!bundle) {
		bundle = await i18n.load(); // potentially use the packageName parameter too
	}
	return {
		getText: (keyObj, ...params) => {
			return bundle.get(keyObj.key, params, keyObj.defaultText);
		},
	};
});

@alex-zhang
Copy link

hi, @vladitasev

thanks~, It's looks good for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants