diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index b084b32bd1f1..422784751761 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -925,30 +925,6 @@ license. The following developer guide rules are specific for working with the React framework. -#### Prefer reactDirective over react-component - -When using `ngReact` to embed your react components inside Angular HTML, prefer the -`reactDirective` service over the `react-component` directive. -You can read more about these two ngReact methods [here](https://github.com/ngReact/ngReact#features). - -Using `react-component` means adding a bunch of components into angular, while `reactDirective` keeps them isolated, and is also a more succinct syntax. - -**Good:** - -```html - -``` - -**Bad:** - -```html - -``` - #### Name action functions and prop functions appropriately Name action functions in the form of a strong verb and passed properties in the form of on. E.g: diff --git a/packages/osd-i18n/GUIDELINE.md b/packages/osd-i18n/GUIDELINE.md index ae5b2b5ca298..ac2fb0047c9b 100644 --- a/packages/osd-i18n/GUIDELINE.md +++ b/packages/osd-i18n/GUIDELINE.md @@ -92,17 +92,6 @@ The long term plan is to rely on using `FormattedMessage` and `i18n.translate()` Currently, we support the following ReactJS `i18n` tools, but they will be removed in future releases: - Usage of `props.intl.formatmessage()` (where `intl` is passed to `props` by `injectI18n` HOC). -#### In AngularJS - -The long term plan is to rely on using `i18n.translate()` by statically importing `i18n` from the `@osd/i18n` package. **Avoid using the `i18n` filter and the `i18n` service injected in controllers, directives, services.** - -- Call JS function `i18n.translate()` from the `@osd/i18n` package. -- Use `i18nId` directive in template. - -Currently, we support the following AngluarJS `i18n` tools, but they will be removed in future releases: -- Usage of `i18n` service in controllers, directives, services by injecting it. -- Usage of `i18n` filter in template for attribute translation. Note: Use one-time binding ("{{:: ... }}") in filters wherever it's possible to prevent unnecessary expression re-evaluation. - #### In JavaScript - Use `i18n.translate()` in NodeJS or any other framework agnostic code, where `i18n` is the I18n engine from `@osd/i18n` package. diff --git a/packages/osd-i18n/README.md b/packages/osd-i18n/README.md index 16e740e925d5..a58a15ab4fd9 100644 --- a/packages/osd-i18n/README.md +++ b/packages/osd-i18n/README.md @@ -1,14 +1,13 @@ # I18n -OpenSearch Dashboards relies on several UI frameworks (ReactJS and AngularJS) and +OpenSearch Dashboards relies on UI frameworks (ReactJS) and requires localization in different environments (browser and NodeJS). Internationalization engine is framework agnostic and consumable in -all parts of OpenSearch Dashboards (ReactJS, AngularJS and NodeJS). In order to simplify +all parts of OpenSearch Dashboards (ReactJS and NodeJS). In order to simplify internationalization in UI frameworks, the additional abstractions are -built around the I18n engine: `react-intl` for React and custom -components for AngularJS. [React-intl](https://github.com/yahoo/react-intl) +built around the I18n engine: `react-intl` for React. [React-intl](https://github.com/yahoo/react-intl) is built around [intl-messageformat](https://github.com/yahoo/intl-messageformat), -so both React and AngularJS frameworks use the same engine and the same +so the React framework uses the same engine and the same message syntax. ## Localization files @@ -343,98 +342,6 @@ export const MyComponent = injectI18n( ); ``` -## AngularJS - -The long term plan is to rely on using `i18n.translate()` by statically importing `i18n` from the `@osd/i18n` package. **Avoid using the `i18n` filter and the `i18n` service injected in controllers, directives, services.** - -AngularJS wrapper has 4 entities: translation `provider`, `service`, `directive` -and `filter`. Both the directive and the filter use the translation `service` -with i18n engine under the hood. - -The translation `provider` is used for `service` configuration and -has the following methods: -- `addMessages(messages: Map, [locale: string])` - provides a way to register -translations with the library -- `setLocale(locale: string)` - tells the library which language to use by given -language key -- `getLocale()` - returns the current locale -- `setDefaultLocale(locale: string)` - tells the library which language to fallback -when missing translations -- `getDefaultLocale()` - returns the default locale -- `setFormats(formats: object)` - supplies a set of options to the underlying formatter -- `getFormats()` - returns current formats -- `getRegisteredLocales()` - returns array of locales having translations -- `init(messages: Map)` - initializes the engine - -The translation `service` provides only one method: -- `i18n(id: string, { values: object, defaultMessage: string, description: string })` – -translate message by id - -The translation `filter` is used for attributes translation and has -the following syntax: -``` -{{ ::'translationId' | i18n: { values: object, defaultMessage: string, description: string } }} -``` - -Where: -- `translationId` - translation id to be translated -- `values` - values to pass into translation -- `defaultMessage` - will be used unless translation was successful (the final - fallback in english, will be used for generating `en.json`) -- `description` - optional context comment that will be extracted by i18n tools -and added as a comment next to translation message at `defaultMessages.json` - -The translation `directive` has the following syntax: -```html - -``` - -Where: -- `i18n-id` - translation id to be translated -- `i18n-default-message` - will be used unless translation was successful -- `i18n-values` - values to pass into translation -- `i18n-description` - optional context comment that will be extracted by i18n tools -and added as a comment next to translation message at `defaultMessages.json` - -If HTML rendering in `i18n-values` is required then value key in `i18n-values` object -should have `html_` prefix. Otherwise the value will be inserted to the message without -HTML rendering.\ -Example: -```html -

-``` - -Angular `I18n` module is placed into `autoload` module, so it will be -loaded automatically. After that we can use i18n directive in Angular templates: -```html - -``` - -In order to translate attributes in AngularJS we should use `i18nFilter`: -```html - -``` - ## I18n tools In order to simplify localization process, some additional tools were implemented: diff --git a/packages/osd-i18n/angular/package.json b/packages/osd-i18n/angular/package.json deleted file mode 100644 index 1979e988fa7c..000000000000 --- a/packages/osd-i18n/angular/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "browser": "../target/web/angular", - "main": "../target/node/angular", - "types": "../target/types/angular/index.d.ts" -} diff --git a/src/dev/i18n/README.md b/src/dev/i18n/README.md index 4a9e3f45eff0..7cfb938d38ec 100644 --- a/src/dev/i18n/README.md +++ b/src/dev/i18n/README.md @@ -4,7 +4,7 @@ ### Description -The tool is used to extract default messages from all `*.{js, ts, jsx, tsx, html }` files in provided plugins directories to a JSON file. +The tool is used to extract default messages from all `*.{js, ts, jsx, tsx }` files in provided plugins directories to a JSON file. It uses Babel to parse code and build an AST for each file or a single JS expression if whole file parsing is impossible. The tool is able to validate, extract and match IDs, default messages and descriptions only if they are defined statically and together, otherwise it will fail with detailed explanation. That means one can't define ID in one place and default message in another, or use function call to dynamically create default message etc. @@ -18,33 +18,6 @@ The `defaultMessage` must contain ICU references to all keys in the `values` and The `description` is optional, `values` is optional too unless `defaultMessage` references to it. -* **Angular (.html)** - - * **Filter** - - ``` - {{ ::'pluginNamespace.messageId' | i18n: { - defaultMessage: 'Default message string literal, {key}', - values: { key: 'value' }, - description: 'Message context or description' - } }} - ``` - - * Don't break `| i18n: {` with line breaks, and don't skip whitespaces around `i18n:`. - * `::` operator is optional. Omit it if you need data binding for the `values`. - - * **Directive** - - ```html -

- ``` - - * `html_` prefixes will be removed from `i18n-values` keys before validation. * **React (.jsx, .tsx)** diff --git a/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_1/test_file_4.html b/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_1/test_file_4.html deleted file mode 100644 index f725fa288405..000000000000 --- a/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_1/test_file_4.html +++ /dev/null @@ -1,8 +0,0 @@ - -
-
-
-
diff --git a/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2/test_file.html b/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2/test_file.html deleted file mode 100644 index c12843602b13..000000000000 --- a/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2/test_file.html +++ /dev/null @@ -1 +0,0 @@ -

{{ ::'plugin_2.message-id' | i18n: { defaultMessage: 'Message text' } }}

diff --git a/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2/test_file.jsx b/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2/test_file.jsx new file mode 100644 index 000000000000..b3f0c8d2b9c1 --- /dev/null +++ b/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2/test_file.jsx @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +i18n('plugin_2.message-id', { defaultMessage: 'Message text' }); diff --git a/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_4/test_file_4.jsx b/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_4/test_file_4.jsx new file mode 100644 index 000000000000..5ce7b916bcd4 --- /dev/null +++ b/src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_4/test_file_4.jsx @@ -0,0 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable */ +class Component extends PureComponent { + render() { + return ( +
+ +
+ ); + } +} \ No newline at end of file diff --git a/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap b/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap index 68ed0433f1ae..cf91d6252d05 100644 --- a/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap +++ b/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap @@ -33,6 +33,54 @@ Array [ ] `; +exports[`dev/i18n/extract_default_translations extracts messages from path to map 2`] = ` +Array [ + Array [ + "plugin_2.message-id", + Object { + "description": undefined, + "message": "Message text", + }, + ], +] +`; + +exports[`dev/i18n/extract_default_translations extracts messages from path to map 3`] = ` +Array [ + Array [ + "plugin_3.duplicate_id", + Object { + "description": undefined, + "message": "Message 1", + }, + ], +] +`; + +exports[`dev/i18n/extract_default_translations extracts messages from path to map 4`] = ` +Array [ + Array [ + "plugin_3.duplicate_id", + Object { + "description": undefined, + "message": "Message 1", + }, + ], +] +`; + +exports[`dev/i18n/extract_default_translations extracts messages from path to map 5`] = ` +Array [ + Array [ + "plugin_4.id_1", + Object { + "description": undefined, + "message": "Message 4", + }, + ], +] +`; + exports[`dev/i18n/extract_default_translations throws on id collision 1`] = ` Array [ " I18N ERROR  Error in src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_3/test_file.jsx diff --git a/src/dev/i18n/extract_default_translations.test.js b/src/dev/i18n/extract_default_translations.test.js index ea4754f3645e..c995ec562735 100644 --- a/src/dev/i18n/extract_default_translations.test.js +++ b/src/dev/i18n/extract_default_translations.test.js @@ -42,6 +42,7 @@ const pluginsPaths = [ path.join(fixturesPath, 'test_plugin_2'), path.join(fixturesPath, 'test_plugin_3'), path.join(fixturesPath, 'test_plugin_3_additional_path'), + path.join(fixturesPath, 'test_plugin_4'), ]; const config = { @@ -52,17 +53,19 @@ const config = { 'src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_3', 'src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_3_additional_path', ], + plugin_4: ['src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_4'], }, exclude: [], }; describe('dev/i18n/extract_default_translations', () => { test('extracts messages from path to map', async () => { - const [pluginPath] = pluginsPaths; - const resultMap = new Map(); + for (const pluginPath of pluginsPaths) { + const resultMap = new Map(); - await extractMessagesFromPathToMap(pluginPath, resultMap, config, new ErrorReporter()); - expect([...resultMap].sort()).toMatchSnapshot(); + await extractMessagesFromPathToMap(pluginPath, resultMap, config, new ErrorReporter()); + expect([...resultMap].sort()).toMatchSnapshot(); + } }); test('throws on id collision', async () => { @@ -88,11 +91,11 @@ describe('dev/i18n/extract_default_translations', () => { const id = 'plugin_3.message-id'; const filePath1 = path.resolve( __dirname, - '__fixtures__/extract_default_translations/test_plugin_3/test_file.html' + '__fixtures__/extract_default_translations/test_plugin_3/test_file.jsx' ); const filePath2 = path.resolve( __dirname, - '__fixtures__/extract_default_translations/test_plugin_3_additional_path/test_file.html' + '__fixtures__/extract_default_translations/test_plugin_3_additional_path/test_file.jsx' ); expect(() => validateMessageNamespace(id, filePath1, config.paths)).not.toThrow(); expect(() => validateMessageNamespace(id, filePath2, config.paths)).not.toThrow(); @@ -103,7 +106,7 @@ describe('dev/i18n/extract_default_translations', () => { const id = 'wrong_plugin_namespace.message-id'; const filePath = path.resolve( __dirname, - '__fixtures__/extract_default_translations/test_plugin_2/test_file.html' + '__fixtures__/extract_default_translations/test_plugin_2/test_file.jsx' ); expect(() => validateMessageNamespace(id, filePath, config.paths, { report })).not.toThrow();