From f14c5c598f5cfcd2fe4dcfade45027e75ad5e5e2 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Sun, 3 Feb 2019 05:44:28 -0800 Subject: [PATCH 01/12] support automatic i18n string translation to pseudo code for debugging --- src-docs/src/actions/locale_actions.js | 8 ++ .../guide_locale_selector.js | 87 +++++++++++++++ .../components/guide_locale_selector/index.js | 1 + .../string/pseudo_locale_translator.ts | 104 ++++++++++++++++++ src-docs/src/store/reducers/locale_reducer.js | 20 ++++ 5 files changed, 220 insertions(+) create mode 100644 src-docs/src/actions/locale_actions.js create mode 100644 src-docs/src/components/guide_locale_selector/guide_locale_selector.js create mode 100644 src-docs/src/components/guide_locale_selector/index.js create mode 100644 src-docs/src/services/string/pseudo_locale_translator.ts create mode 100644 src-docs/src/store/reducers/locale_reducer.js diff --git a/src-docs/src/actions/locale_actions.js b/src-docs/src/actions/locale_actions.js new file mode 100644 index 00000000000..17e84a49056 --- /dev/null +++ b/src-docs/src/actions/locale_actions.js @@ -0,0 +1,8 @@ +import ActionTypes from './action_types'; + +export const toggleLocale = locale => ({ + type: ActionTypes.TOGGLE_LOCALE, + data: { + locale, + }, +}); diff --git a/src-docs/src/components/guide_locale_selector/guide_locale_selector.js b/src-docs/src/components/guide_locale_selector/guide_locale_selector.js new file mode 100644 index 00000000000..1b3010aea41 --- /dev/null +++ b/src-docs/src/components/guide_locale_selector/guide_locale_selector.js @@ -0,0 +1,87 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { + EuiButtonEmpty, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, +} from '../../../../src/components'; + +export class GuideLocaleSelector extends Component { + constructor(props) { + super(props); + + this.state = { + isLocalePopoverOpen: false, + }; + } + + onLocaleButtonClick = () => { + this.setState({ + isLocalePopoverOpen: !this.state.isLocalePopoverOpen, + }); + }; + + closeLocalePopover = () => { + this.setState({ + isLocalePopoverOpen: false, + }); + }; + + render() { + const localeButton = ( + + {this.props.selectedLocale} + + ); + + const localeOptions = [{ + name: 'English', + value: 'en', + }, { + name: 'Pseudo-English', + value: 'en-xa', + }].map(option => { + const { name, value } = option; + + return ( + { this.closeLocalePopover(); this.props.onToggleLocale(value); }} + > + {`${name}`} + + ); + }); + + return ( + + + + ); + } +} + +GuideLocaleSelector.propTypes = { + onToggleLocale: PropTypes.func.isRequired, + selectedLocale: PropTypes.string.isRequired, +}; diff --git a/src-docs/src/components/guide_locale_selector/index.js b/src-docs/src/components/guide_locale_selector/index.js new file mode 100644 index 00000000000..7f4a4fbcfb4 --- /dev/null +++ b/src-docs/src/components/guide_locale_selector/index.js @@ -0,0 +1 @@ +export { GuideLocaleSelector } from './guide_locale_selector'; diff --git a/src-docs/src/services/string/pseudo_locale_translator.ts b/src-docs/src/services/string/pseudo_locale_translator.ts new file mode 100644 index 00000000000..3fb739be6b3 --- /dev/null +++ b/src-docs/src/services/string/pseudo_locale_translator.ts @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Matches every single [A-Za-z] character, ``, `](markdown-link-address)` and `@I18N@valid_variable_name@I18N@` + */ +const CHARS_FOR_PSEUDO_LOCALIZATION_REGEX = /[A-Za-z]|(\]\([\s\S]*?\))|(<([^"<>]|("[^"]*?"))*?>)|(@I18N@\w*?@I18N@)/g; +const PSEUDO_ACCENTS_LOCALE = 'en-xa'; + +export function isPseudoLocale(locale: string) { + return locale.toLowerCase() === PSEUDO_ACCENTS_LOCALE; +} + +/** + * Replaces every latin char by pseudo char and repeats every third char twice. + */ +function replacer() { + let count = 0; + + return (match: string) => { + // if `match.length !== 1`, then `match` is html tag or markdown link address, so it should be ignored + if (match.length !== 1) { + return match; + } + + const pseudoChar = pseudoAccentCharMap[match] || match; + return ++count % 3 === 0 ? pseudoChar.repeat(2) : pseudoChar; + }; +} + +export function translateUsingPseudoLocale(message: string) { + return message.replace(CHARS_FOR_PSEUDO_LOCALIZATION_REGEX, replacer()); +} + +const pseudoAccentCharMap: Record = { + a: 'à', + b: 'ƀ', + c: 'ç', + d: 'ð', + e: 'é', + f: 'ƒ', + g: 'ĝ', + h: 'ĥ', + i: 'î', + l: 'ļ', + k: 'ķ', + j: 'ĵ', + m: 'ɱ', + n: 'ñ', + o: 'ô', + p: 'þ', + q: 'ǫ', + r: 'ŕ', + s: 'š', + t: 'ţ', + u: 'û', + v: 'ṽ', + w: 'ŵ', + x: 'ẋ', + y: 'ý', + z: 'ž', + A: 'À', + B: 'Ɓ', + C: 'Ç', + D: 'Ð', + E: 'É', + F: 'Ƒ', + G: 'Ĝ', + H: 'Ĥ', + I: 'Î', + L: 'Ļ', + K: 'Ķ', + J: 'Ĵ', + M: 'Ṁ', + N: 'Ñ', + O: 'Ô', + P: 'Þ', + Q: 'Ǫ', + R: 'Ŕ', + S: 'Š', + T: 'Ţ', + U: 'Û', + V: 'Ṽ', + W: 'Ŵ', + X: 'Ẋ', + Y: 'Ý', + Z: 'Ž', +}; diff --git a/src-docs/src/store/reducers/locale_reducer.js b/src-docs/src/store/reducers/locale_reducer.js new file mode 100644 index 00000000000..6a2bf5a78c5 --- /dev/null +++ b/src-docs/src/store/reducers/locale_reducer.js @@ -0,0 +1,20 @@ +import ActionTypes from '../../actions/action_types'; + +const defaultState = { + locale: 'en', +}; + +export default function localeReducer(state = defaultState, action) { + switch (action.type) { + case ActionTypes.TOGGLE_LOCALE: { + return { + locale: action.data.locale, + }; + } + + default: + break; + } + + return state; +} From 44114512099170efb6fc935c14dd4f3f4408fcfb Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Sun, 3 Feb 2019 05:58:33 -0800 Subject: [PATCH 02/12] support automatic i18n string translation to pseudo code for debugging --- src-docs/src/actions/action_types.js | 3 +++ src-docs/src/actions/index.js | 4 ++++ .../guide_page/guide_page_chrome.js | 11 ++++++++++ src-docs/src/services/index.js | 2 ++ .../string/pseudo_locale_translator.ts | 4 +++- src-docs/src/store/configure_store.js | 2 ++ src-docs/src/store/index.js | 4 ++++ src-docs/src/views/app_container.js | 4 ++++ src-docs/src/views/app_view.js | 21 ++++++++++++++++++- src/components/context/context.tsx | 1 + src/components/i18n/i18n.tsx | 18 ++++++++++++---- 11 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src-docs/src/actions/action_types.js b/src-docs/src/actions/action_types.js index 737b33e0460..412b911becb 100644 --- a/src-docs/src/actions/action_types.js +++ b/src-docs/src/actions/action_types.js @@ -7,4 +7,7 @@ export default keyMirror({ // Theme actions TOGGLE_THEME: null, + + // Locale actions + TOGGLE_LOCALE: null, }); diff --git a/src-docs/src/actions/index.js b/src-docs/src/actions/index.js index d3ac88f393f..4e134918f52 100644 --- a/src-docs/src/actions/index.js +++ b/src-docs/src/actions/index.js @@ -1,3 +1,7 @@ export { toggleTheme, } from './theme_actions'; + +export { + toggleLocale, +} from './locale_actions'; \ No newline at end of file diff --git a/src-docs/src/components/guide_page/guide_page_chrome.js b/src-docs/src/components/guide_page/guide_page_chrome.js index c60ec28488e..2015a36703a 100644 --- a/src-docs/src/components/guide_page/guide_page_chrome.js +++ b/src-docs/src/components/guide_page/guide_page_chrome.js @@ -16,6 +16,9 @@ import { EuiText, } from '../../../../src/components'; +import { + GuideLocaleSelector, +} from '../guide_locale_selector'; import { GuideThemeSelector, } from '../guide_theme_selector'; @@ -89,6 +92,12 @@ export class GuidePageChrome extends Component { selectedTheme={this.props.selectedTheme} /> + + + ); } @@ -198,5 +207,7 @@ GuidePageChrome.propTypes = { currentRouteName: PropTypes.string, onToggleTheme: PropTypes.func.isRequired, selectedTheme: PropTypes.string.isRequired, + onToggleLocale: PropTypes.func.isRequired, + selectedLocale: PropTypes.string.isRequired, navigation: PropTypes.array.isRequired, }; diff --git a/src-docs/src/services/index.js b/src-docs/src/services/index.js index c7a3a967ba4..92b15f18701 100644 --- a/src-docs/src/services/index.js +++ b/src-docs/src/services/index.js @@ -1,5 +1,7 @@ export { renderToHtml } from './string/render_to_html'; +export { translateUsingPseudoLocale } from './string/pseudo_locale_translator'; + export { registerTheme, applyTheme, diff --git a/src-docs/src/services/string/pseudo_locale_translator.ts b/src-docs/src/services/string/pseudo_locale_translator.ts index 3fb739be6b3..6ef04efbe3f 100644 --- a/src-docs/src/services/string/pseudo_locale_translator.ts +++ b/src-docs/src/services/string/pseudo_locale_translator.ts @@ -18,8 +18,10 @@ */ /** - * Matches every single [A-Za-z] character, ``, `](markdown-link-address)` and `@I18N@valid_variable_name@I18N@` + * Matches every single [A-Za-z] character, ``, `](markdown-link-address)` and + * `@I18N@valid_variable_name@I18N@` */ + const CHARS_FOR_PSEUDO_LOCALIZATION_REGEX = /[A-Za-z]|(\]\([\s\S]*?\))|(<([^"<>]|("[^"]*?"))*?>)|(@I18N@\w*?@I18N@)/g; const PSEUDO_ACCENTS_LOCALE = 'en-xa'; diff --git a/src-docs/src/store/configure_store.js b/src-docs/src/store/configure_store.js index d8cc0fa28fd..6dc78577140 100644 --- a/src-docs/src/store/configure_store.js +++ b/src-docs/src/store/configure_store.js @@ -12,6 +12,7 @@ import { import Routes from '../routes'; +import localeReducer from './reducers/locale_reducer'; import themeReducer from './reducers/theme_reducer'; /** @@ -23,6 +24,7 @@ export default function configureStore(initialState) { return { routing: routerReducer(state.routing, action), theme: themeReducer(state.theme, action), + locale: localeReducer(state.locale, action), routes: Routes, }; } diff --git a/src-docs/src/store/index.js b/src-docs/src/store/index.js index a9df2b1f466..3b387c4b2c4 100644 --- a/src-docs/src/store/index.js +++ b/src-docs/src/store/index.js @@ -5,3 +5,7 @@ export function getTheme(state) { export function getRoutes(state) { return state.routes; } + +export function getLocale(state) { + return state.locale.locale; +} \ No newline at end of file diff --git a/src-docs/src/views/app_container.js b/src-docs/src/views/app_container.js index 5276db0615c..36db4253313 100644 --- a/src-docs/src/views/app_container.js +++ b/src-docs/src/views/app_container.js @@ -5,10 +5,12 @@ import { AppView } from './app_view'; import { getTheme, getRoutes, + getLocale, } from '../store'; import { toggleTheme, + toggleLocale, } from '../actions'; function mapStateToProps(state, ownProps) { @@ -16,6 +18,7 @@ function mapStateToProps(state, ownProps) { routes: ownProps.routes, currentRoute: ownProps.routes[1], theme: getTheme(state), + locale: getLocale(state), routes: getRoutes(state), }; } @@ -24,6 +27,7 @@ export const AppContainer = connect( mapStateToProps, { toggleTheme, + toggleLocale, }, )(AppView); diff --git a/src-docs/src/views/app_view.js b/src-docs/src/views/app_view.js index f7c245d71f3..5d44884d7a8 100644 --- a/src-docs/src/views/app_view.js +++ b/src-docs/src/views/app_view.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { applyTheme, + translateUsingPseudoLocale } from '../services'; import { @@ -13,6 +14,7 @@ import { EuiErrorBoundary, EuiPage, EuiPageBody, + EuiContext } from '../../../src/components'; import { keyCodes } from '../../../src/services'; @@ -46,11 +48,22 @@ export class AppView extends Component { currentRoute, toggleTheme, theme, + toggleLocale, + locale, routes, } = this.props; const { navigation } = routes; + const mappingFuncs = { + 'en-xa': translateUsingPseudoLocale + }; + + const i18n = { + mappingFunc: mappingFuncs[locale], + formatNumber: (value) => new Intl.NumberFormat(locale).format(value), + }; + return ( @@ -59,12 +72,16 @@ export class AppView extends Component { currentRouteName={currentRoute.name} onToggleTheme={toggleTheme} selectedTheme={theme} + onToggleLocale={toggleLocale} + selectedLocale={locale} navigation={navigation} />
- {children} + + {children} +
@@ -117,6 +134,8 @@ AppView.propTypes = { currentRoute: PropTypes.object.isRequired, theme: PropTypes.string.isRequired, toggleTheme: PropTypes.func.isRequired, + locale: PropTypes.string.isRequired, + toggleLocale: PropTypes.func.isRequired, routes: PropTypes.object.isRequired, }; diff --git a/src/components/context/context.tsx b/src/components/context/context.tsx index 7f610ec3421..5133185841d 100644 --- a/src/components/context/context.tsx +++ b/src/components/context/context.tsx @@ -10,6 +10,7 @@ export interface I18nShape { mapping?: { [key: string]: Renderable; }; + mappingFunc?: (value: string) => string; formatNumber?: (x: number) => string; formatDateTime?: (x: Date) => string; } diff --git a/src/components/i18n/i18n.tsx b/src/components/i18n/i18n.tsx index 885c4e176df..8851c0d98b8 100644 --- a/src/components/i18n/i18n.tsx +++ b/src/components/i18n/i18n.tsx @@ -12,9 +12,11 @@ function lookupToken( token: string, i18nMapping: I18nShape['mapping'], valueDefault: Renderable, + i18nMappingFunc?: (token: string) => string, values?: I18nTokenShape['values'] ): ReactChild { - const renderable = (i18nMapping && i18nMapping[token]) || valueDefault; + let renderable = (i18nMapping && i18nMapping[token]) || valueDefault; + if (typeof renderable === 'function') { if (values === undefined) { return throwError(); @@ -22,11 +24,18 @@ function lookupToken( return renderable(values); } } else if (values === undefined || typeof renderable !== 'string') { + + if (i18nMappingFunc && typeof valueDefault === 'string') { + renderable = i18nMappingFunc(valueDefault); + } return renderable; } const children = processStringToChildren(renderable, values); if (typeof children === 'string') { + if (i18nMappingFunc) { + renderable = i18nMappingFunc(children); + } return children; } @@ -62,12 +71,13 @@ const EuiI18n = (props: EuiI18nProps) => ( { (i18nConfig) => { - const { mapping } = i18nConfig; + const { mapping, mappingFunc } = i18nConfig; if (hasTokens(props)) { - return props.children(props.tokens.map((token, idx) => lookupToken(token, mapping, props.defaults[idx]))); + return props.children(props.tokens.map((token, idx) => + lookupToken(token, mapping, props.defaults[idx], mappingFunc))); } - const tokenValue = lookupToken(props.token, mapping, props.default, props.values); + const tokenValue = lookupToken(props.token, mapping, props.default, mappingFunc, props.values); if (props.children) { return props.children(tokenValue); } else { From 2cba98ecc362f0a06810dc985e44718e106b042c Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Sun, 3 Feb 2019 07:56:57 -0800 Subject: [PATCH 03/12] added script to fetch all i18n strings from components --- src-docs/src/actions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/actions/index.js b/src-docs/src/actions/index.js index 4e134918f52..f953685a5c2 100644 --- a/src-docs/src/actions/index.js +++ b/src-docs/src/actions/index.js @@ -4,4 +4,4 @@ export { export { toggleLocale, -} from './locale_actions'; \ No newline at end of file +} from './locale_actions'; From ba324738e747c695016505f3c77692b6db7c2076 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Sun, 3 Feb 2019 07:58:20 -0800 Subject: [PATCH 04/12] added script to fetch all i18n strings from components --- scripts/babel/fetch-i18n-strings.js | 98 +++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 scripts/babel/fetch-i18n-strings.js diff --git a/scripts/babel/fetch-i18n-strings.js b/scripts/babel/fetch-i18n-strings.js new file mode 100644 index 00000000000..623d8c3692b --- /dev/null +++ b/scripts/babel/fetch-i18n-strings.js @@ -0,0 +1,98 @@ +const babel = require('@babel/core'); +const babelOptions = require('../../.babelrc'); +const fs = require('fs'); + +const { basename, extname, resolve } = require('path'); +const { readdir, stat } = require('fs').promises; + + +const folder = './src/components/'; +const suffixes = ['.js', '.jsx', '.ts', '.tsx']; +// const filepath = './src/components/combo_box/combo_box_options_list/combo_box_options_list.js'; + +const tokenMapping = {}; + +function handleJSXPath(path) { + if (path.node.name.name === "EuiI18n") { + let token = path.node.attributes.filter( + node => { return node.name.name === "token" } + ); + let defString = path.node.attributes.filter( + node => { return node.name.name === "default" } + ); + + try { + token = token[0].value.value; + } catch (e) { + token = null; + } + try { + defString = defString[0].value.value; + } catch (e) { + defString = null; + } + + return { + token, + defString + }; + } +} + +function traverseFile(filepath) { + const source = fs.readFileSync(filepath); + const ast = babel.parse( + source, + { + ...babelOptions, + filename: basename(filepath), + ast: true + } + ); + + babel.traverse( + ast, + { + // Identifier(path) { + // console.log(path.name) + // }, + JSXOpeningElement(path){ + if (path.node.name.name === "EuiI18n") { + const { + token, + defString + } = handleJSXPath(path); + if (token) { + tokenMapping[token] = defString; + } + } + } + } + ); + console.log(`DONE handling ${basename(filepath)}`) + + +} + +async function getFiles(dir) { + const subdirs = await readdir(dir); + const files = await Promise.all(subdirs.map(async (subdir) => { + const res = resolve(dir, subdir); + return (await stat(res)).isDirectory() ? getFiles(res) : res; + })); + return Array.prototype.concat(...files); +} + +(async () => { + let files = await getFiles(folder); + files = files.filter(filename => { + return suffixes.indexOf(extname(filename)) > -1; + }) + + files.forEach(filename => { + traverseFile(filename) + }) + console.log(tokenMapping); +})(); + +// \ No newline at end of file From 74249e02e4bdc870028144cfdc7c7572145ad7f6 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Mon, 4 Feb 2019 09:36:27 -0700 Subject: [PATCH 05/12] Pseudo-translate I18n string placeholder values --- .../string/pseudo_locale_translator.ts | 36 +------------------ .../i18n/__snapshots__/i18n.test.tsx.snap | 22 ++++++++++++ src/components/i18n/i18n.test.tsx | 21 +++++++++++ src/components/i18n/i18n.tsx | 6 +--- src/components/i18n/i18n_util.test.tsx | 10 ++++++ src/components/i18n/i18n_util.tsx | 10 +++++- 6 files changed, 64 insertions(+), 41 deletions(-) diff --git a/src-docs/src/services/string/pseudo_locale_translator.ts b/src-docs/src/services/string/pseudo_locale_translator.ts index 6ef04efbe3f..e859f6167d1 100644 --- a/src-docs/src/services/string/pseudo_locale_translator.ts +++ b/src-docs/src/services/string/pseudo_locale_translator.ts @@ -1,33 +1,4 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Matches every single [A-Za-z] character, ``, `](markdown-link-address)` and - * `@I18N@valid_variable_name@I18N@` - */ - -const CHARS_FOR_PSEUDO_LOCALIZATION_REGEX = /[A-Za-z]|(\]\([\s\S]*?\))|(<([^"<>]|("[^"]*?"))*?>)|(@I18N@\w*?@I18N@)/g; -const PSEUDO_ACCENTS_LOCALE = 'en-xa'; - -export function isPseudoLocale(locale: string) { - return locale.toLowerCase() === PSEUDO_ACCENTS_LOCALE; -} +const CHARS_FOR_PSEUDO_LOCALIZATION_REGEX = /[a-z]/ig; /** * Replaces every latin char by pseudo char and repeats every third char twice. @@ -36,11 +7,6 @@ function replacer() { let count = 0; return (match: string) => { - // if `match.length !== 1`, then `match` is html tag or markdown link address, so it should be ignored - if (match.length !== 1) { - return match; - } - const pseudoChar = pseudoAccentCharMap[match] || match; return ++count % 3 === 0 ? pseudoChar.repeat(2) : pseudoChar; }; diff --git a/src/components/i18n/__snapshots__/i18n.test.tsx.snap b/src/components/i18n/__snapshots__/i18n.test.tsx.snap index 2892971d76e..baf2a58d676 100644 --- a/src/components/i18n/__snapshots__/i18n.test.tsx.snap +++ b/src/components/i18n/__snapshots__/i18n.test.tsx.snap @@ -135,6 +135,28 @@ exports[`EuiI18n default rendering rendering to dom renders a string with placeh `; +exports[`EuiI18n reading values from context mappingFunc calls the mapping function with the source string 1`] = ` + + +
+ THIS IS THE BASIC STRING. +
+
+
+`; + exports[`EuiI18n reading values from context render prop with multiple tokens renders mapped render prop result to the dom 1`] = ` { expect(component).toMatchSnapshot(); }); }); + + describe('mappingFunc', () => { + it('calls the mapping function with the source string', () => { + const component = mount( + value.toUpperCase(), + }}> + + {(one: ReactChild) =>
{one}
} +
+
+ ); + expect(component).toMatchSnapshot(); + }); + }); }); }); diff --git a/src/components/i18n/i18n.tsx b/src/components/i18n/i18n.tsx index 8851c0d98b8..2401b5b601a 100644 --- a/src/components/i18n/i18n.tsx +++ b/src/components/i18n/i18n.tsx @@ -24,18 +24,14 @@ function lookupToken( return renderable(values); } } else if (values === undefined || typeof renderable !== 'string') { - if (i18nMappingFunc && typeof valueDefault === 'string') { renderable = i18nMappingFunc(valueDefault); } return renderable; } - const children = processStringToChildren(renderable, values); + const children = processStringToChildren(renderable, values, i18nMappingFunc); if (typeof children === 'string') { - if (i18nMappingFunc) { - renderable = i18nMappingFunc(children); - } return children; } diff --git a/src/components/i18n/i18n_util.test.tsx b/src/components/i18n/i18n_util.test.tsx index 513a0c9de2d..98d4ac407cd 100644 --- a/src/components/i18n/i18n_util.test.tsx +++ b/src/components/i18n/i18n_util.test.tsx @@ -27,5 +27,15 @@ describe('i18n_util', () => { expect(processStringToChildren(message, {})).toEqual(message); }); }); + + describe('i18nMappingFunction', () => { + it('calls the mapping function with the source string', () => { + expect(processStringToChildren( + 'Hello, {name}', + { greeting: 'Hello', name: 'John' }, + value => value.toUpperCase() + )).toEqual('HELLO, JOHN'); + }); + }); }); }); diff --git a/src/components/i18n/i18n_util.tsx b/src/components/i18n/i18n_util.tsx index 3bae35eeab4..23baff4dd1f 100644 --- a/src/components/i18n/i18n_util.tsx +++ b/src/components/i18n/i18n_util.tsx @@ -18,9 +18,14 @@ function hasPropName(child: Child): child is ({propName: string}) { * e.g. input:'Hello, {name}' will replace `{name}` with `values[name]` * @param {string} input * @param {RenderableValues} values + * @param {Function} i18nMappingFunc * @returns {string | React.ReactChild[]} */ -export function processStringToChildren(input: string, values: RenderableValues): string | ReactChild[] { +export function processStringToChildren( + input: string, + values: RenderableValues, + i18nMappingFunc?: (token: string) => string +): string | ReactChild[] { const children: ReactChild[] = []; let child: Child; @@ -52,6 +57,9 @@ export function processStringToChildren(input: string, values: RenderableValues) // this won't be called, propName children are converted to a ReactChild before calling this } else { // everything else can go straight in + if (i18nMappingFunc !== undefined && typeof value === 'string') { + value = i18nMappingFunc(value); + } children.push(value); } } From cc14962bd894e89e694d00bd0c1f4e0480b3317b Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Mon, 4 Feb 2019 10:35:22 -0700 Subject: [PATCH 06/12] Update pseudo-translation toggle in docs --- src-docs/src/components/guide_components.scss | 2 +- .../guide_locale_selector.js | 57 +++---------------- .../guide_page/guide_page_chrome.js | 20 ++++--- 3 files changed, 22 insertions(+), 57 deletions(-) diff --git a/src-docs/src/components/guide_components.scss b/src-docs/src/components/guide_components.scss index 5f0ab4dee87..043fff4890f 100644 --- a/src-docs/src/components/guide_components.scss +++ b/src-docs/src/components/guide_components.scss @@ -219,4 +219,4 @@ $guideZLevelHighest: $euiZLevel9 + 1000; .guidePage { display: block; // Fixes IE } -} \ No newline at end of file +} diff --git a/src-docs/src/components/guide_locale_selector/guide_locale_selector.js b/src-docs/src/components/guide_locale_selector/guide_locale_selector.js index 1b3010aea41..6e8eba87fb9 100644 --- a/src-docs/src/components/guide_locale_selector/guide_locale_selector.js +++ b/src-docs/src/components/guide_locale_selector/guide_locale_selector.js @@ -2,10 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { - EuiButtonEmpty, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiPopover, + EuiSwitch, } from '../../../../src/components'; export class GuideLocaleSelector extends Component { @@ -30,53 +27,15 @@ export class GuideLocaleSelector extends Component { }; render() { - const localeButton = ( - - {this.props.selectedLocale} - - ); - - const localeOptions = [{ - name: 'English', - value: 'en', - }, { - name: 'Pseudo-English', - value: 'en-xa', - }].map(option => { - const { name, value } = option; - - return ( - { this.closeLocalePopover(); this.props.onToggleLocale(value); }} - > - {`${name}`} - - ); - }); + const otherLocale = this.props.selectedLocale === 'en' ? 'en-xa' : 'en'; return ( - - - + this.props.onToggleLocale(otherLocale)} + compressed={true} + /> ); } } diff --git a/src-docs/src/components/guide_page/guide_page_chrome.js b/src-docs/src/components/guide_page/guide_page_chrome.js index 2015a36703a..c5d5ba282ef 100644 --- a/src-docs/src/components/guide_page/guide_page_chrome.js +++ b/src-docs/src/components/guide_page/guide_page_chrome.js @@ -81,7 +81,7 @@ export class GuidePageChrome extends Component { ); return ( - + {homeLink} @@ -92,12 +92,18 @@ export class GuidePageChrome extends Component { selectedTheme={this.props.selectedTheme} /> - - - + { + location.host === 'localhost:8030' // eslint-disable-line no-restricted-globals + ? ( + + + + ) + : null + } ); } From 2c03a3ee074c39460548c597f10e4c35cdec9435 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Mon, 4 Feb 2019 12:56:06 -0700 Subject: [PATCH 07/12] write i18n token data to src-docs --- scripts/babel/fetch-i18n-strings.js | 125 ++--- src-docs/src/i18ntokens.json | 722 ++++++++++++++++++++++++++++ 2 files changed, 788 insertions(+), 59 deletions(-) create mode 100644 src-docs/src/i18ntokens.json diff --git a/scripts/babel/fetch-i18n-strings.js b/scripts/babel/fetch-i18n-strings.js index 623d8c3692b..27681942ec3 100644 --- a/scripts/babel/fetch-i18n-strings.js +++ b/scripts/babel/fetch-i18n-strings.js @@ -1,41 +1,58 @@ const babel = require('@babel/core'); const babelOptions = require('../../.babelrc'); const fs = require('fs'); +const { promisify } = require('util'); +const { basename, join, relative } = require('path'); +const glob = require('glob'); +const asyncGlob = promisify(glob); -const { basename, extname, resolve } = require('path'); -const { readdir, stat } = require('fs').promises; +const rootDir = join(__dirname, '..', '..'); +const srcDir = join(rootDir, 'src'); +const tokenMappings = []; -const folder = './src/components/'; -const suffixes = ['.js', '.jsx', '.ts', '.tsx']; -// const filepath = './src/components/combo_box/combo_box_options_list/combo_box_options_list.js'; - -const tokenMapping = {}; +function getCodeForExpression(expressionNode) { + return babel.transformFromAst(babel.types.program([ + babel.types.expressionStatement( + babel.types.removeComments(babel.types.cloneDeep(expressionNode)) + ) + ])).code; +} function handleJSXPath(path) { - if (path.node.name.name === "EuiI18n") { - let token = path.node.attributes.filter( - node => { return node.name.name === "token" } - ); - let defString = path.node.attributes.filter( - node => { return node.name.name === "default" } + if (path.node.name.name === 'EuiI18n') { + const symbols = []; + + const attributes = path.node.attributes.reduce( + (attributes, node) => { + attributes[node.name.name] = node.value; + return attributes; + }, + {} ); - try { - token = token[0].value.value; - } catch (e) { - token = null; - } - try { - defString = defString[0].value.value; - } catch (e) { - defString = null; + if (attributes.hasOwnProperty('token') && attributes.hasOwnProperty('default')) { + const tokenNode = attributes.token; + const defStringNode = attributes.default; + + let defString; + let highlighting; + if (defStringNode.type === 'StringLiteral') { + defString = defStringNode.value; + highlighting = 'string'; + } else if (defStringNode.type === 'JSXExpressionContainer') { + defString = getCodeForExpression(defStringNode.expression); + highlighting = 'code'; + } + symbols.push({ + token: tokenNode.value, + defString, + highlighting, + loc: path.node.loc + }); } - return { - token, - defString - }; + return symbols; } } @@ -53,46 +70,36 @@ function traverseFile(filepath) { babel.traverse( ast, { - // Identifier(path) { - // console.log(path.name) - // }, - JSXOpeningElement(path){ - if (path.node.name.name === "EuiI18n") { - const { - token, - defString - } = handleJSXPath(path); - if (token) { - tokenMapping[token] = defString; + JSXOpeningElement(path) { + if (path.node.name.name === 'EuiI18n') { + const symbols = handleJSXPath(path); + for (let i = 0; i < symbols.length; i++) { + tokenMappings.push( + { ...symbols[i], filepath: relative(rootDir, filepath) } + ); } } } } ); - console.log(`DONE handling ${basename(filepath)}`) - - -} - -async function getFiles(dir) { - const subdirs = await readdir(dir); - const files = await Promise.all(subdirs.map(async (subdir) => { - const res = resolve(dir, subdir); - return (await stat(res)).isDirectory() ? getFiles(res) : res; - })); - return Array.prototype.concat(...files); } (async () => { - let files = await getFiles(folder); - files = files.filter(filename => { - return suffixes.indexOf(extname(filename)) > -1; - }) + const files = (await asyncGlob( + '**/*.@(js|ts|tsx)', + { cwd: srcDir, realpath: true }, + )).filter(filepath => { + if (filepath.endsWith('index.d.ts')) return false; + if (filepath.endsWith('test.ts')) return false; + if (filepath.endsWith('test.tsx')) return false; + if (filepath.endsWith('test.js')) return false; - files.forEach(filename => { - traverseFile(filename) - }) - console.log(tokenMapping); -})(); + return true; + }); -// \ No newline at end of file + files.forEach(filename => traverseFile(filename)); + fs.writeFileSync( + join(rootDir, 'src-docs', 'src', 'i18ntokens.json'), + JSON.stringify(tokenMappings, null, 2) + ); +})(); diff --git a/src-docs/src/i18ntokens.json b/src-docs/src/i18ntokens.json new file mode 100644 index 00000000000..8fab3e4ea14 --- /dev/null +++ b/src-docs/src/i18ntokens.json @@ -0,0 +1,722 @@ +[ + { + "token": "euiBasicTable.tableDescription", + "defString": "Below is a table of {itemCount} items.", + "highlighting": "string", + "loc": { + "start": { + "line": 422, + "column": 10 + }, + "end": { + "line": 426, + "column": 12 + } + }, + "filepath": "src/components/basic_table/basic_table.js" + }, + { + "token": "euiBasicTable.selectAllRows", + "defString": "Select all rows", + "highlighting": "string", + "loc": { + "start": { + "line": 459, + "column": 10 + }, + "end": { + "line": 459, + "column": 81 + } + }, + "filepath": "src/components/basic_table/basic_table.js" + }, + { + "token": "euiBasicTable.selectThisRow", + "defString": "Select this row", + "highlighting": "string", + "loc": { + "start": { + "line": 737, + "column": 8 + }, + "end": { + "line": 737, + "column": 79 + } + }, + "filepath": "src/components/basic_table/basic_table.js" + }, + { + "token": "euiCollapsedItemActions.allActions", + "defString": "All actions", + "highlighting": "string", + "loc": { + "start": { + "line": 92, + "column": 6 + }, + "end": { + "line": 92, + "column": 80 + } + }, + "filepath": "src/components/basic_table/collapsed_item_actions.js" + }, + { + "token": "euiCollapsedItemActions.allActions", + "defString": "All actions", + "highlighting": "string", + "loc": { + "start": { + "line": 108, + "column": 6 + }, + "end": { + "line": 108, + "column": 80 + } + }, + "filepath": "src/components/basic_table/collapsed_item_actions.js" + }, + { + "token": "euiBottomBar.screenReaderAnnouncement", + "defString": "There is a new menu opening with page level controls at the bottom of the document.", + "highlighting": "string", + "loc": { + "start": { + "line": 57, + "column": 12 + }, + "end": { + "line": 60, + "column": 14 + } + }, + "filepath": "src/components/bottom_bar/bottom_bar.js" + }, + { + "token": "euiCodeEditor.startInteracting", + "defString": "Press Enter to start interacting with the code.", + "highlighting": "string", + "loc": { + "start": { + "line": 161, + "column": 16 + }, + "end": { + "line": 164, + "column": 18 + } + }, + "filepath": "src/components/code_editor/code_editor.js" + }, + { + "token": "euiCodeEditor.startEditing", + "defString": "Press Enter to start editing.", + "highlighting": "string", + "loc": { + "start": { + "line": 167, + "column": 16 + }, + "end": { + "line": 170, + "column": 18 + } + }, + "filepath": "src/components/code_editor/code_editor.js" + }, + { + "token": "euiCodeEditor.stopInteracting", + "defString": "When you're done, press Escape to stop interacting with the code.", + "highlighting": "string", + "loc": { + "start": { + "line": 179, + "column": 16 + }, + "end": { + "line": 182, + "column": 18 + } + }, + "filepath": "src/components/code_editor/code_editor.js" + }, + { + "token": "euiCodeEditor.stopEditing", + "defString": "When you're done, press Escape to stop editing.", + "highlighting": "string", + "loc": { + "start": { + "line": 185, + "column": 16 + }, + "end": { + "line": 188, + "column": 18 + } + }, + "filepath": "src/components/code_editor/code_editor.js" + }, + { + "token": "euiColorPicker.transparentColor", + "defString": "transparent", + "highlighting": "string", + "loc": { + "start": { + "line": 35, + "column": 6 + }, + "end": { + "line": 38, + "column": 7 + } + }, + "filepath": "src/components/color_picker/color_picker.js" + }, + { + "token": "euiColorPicker.colorSelectionLabel", + "defString": "Color selection is {colorValue}", + "highlighting": "string", + "loc": { + "start": { + "line": 43, + "column": 12 + }, + "end": { + "line": 47, + "column": 13 + } + }, + "filepath": "src/components/color_picker/color_picker.js" + }, + { + "token": "euiComboBoxPill.removeSelection", + "defString": "Remove {children} from selection in this group", + "highlighting": "string", + "loc": { + "start": { + "line": 51, + "column": 8 + }, + "end": { + "line": 55, + "column": 9 + } + }, + "filepath": "src/components/combo_box/combo_box_input/combo_box_pill.js" + }, + { + "token": "euiComboBoxOptionsList.loadingOptions", + "defString": "Loading options", + "highlighting": "string", + "loc": { + "start": { + "line": 133, + "column": 12 + }, + "end": { + "line": 133, + "column": 94 + } + }, + "filepath": "src/components/combo_box/combo_box_options_list/combo_box_options_list.js" + }, + { + "token": "euiComboBoxOptionsList.alreadyAdded", + "defString": "{label} has already been added", + "highlighting": "string", + "loc": { + "start": { + "line": 144, + "column": 14 + }, + "end": { + "line": 148, + "column": 16 + } + }, + "filepath": "src/components/combo_box/combo_box_options_list/combo_box_options_list.js" + }, + { + "token": "euiComboBoxOptionsList.createCustomOption", + "defString": "Hit {key} to add {searchValue} as a custom option", + "highlighting": "string", + "loc": { + "start": { + "line": 154, + "column": 14 + }, + "end": { + "line": 158, + "column": 16 + } + }, + "filepath": "src/components/combo_box/combo_box_options_list/combo_box_options_list.js" + }, + { + "token": "euiComboBoxOptionsList.noMatchingOptions", + "defString": "{searchValue} doesn't match any options", + "highlighting": "string", + "loc": { + "start": { + "line": 165, + "column": 12 + }, + "end": { + "line": 169, + "column": 14 + } + }, + "filepath": "src/components/combo_box/combo_box_options_list/combo_box_options_list.js" + }, + { + "token": "euiComboBoxOptionsList.noAvailableOptions", + "defString": "There aren't any options available", + "highlighting": "string", + "loc": { + "start": { + "line": 176, + "column": 10 + }, + "end": { + "line": 176, + "column": 115 + } + }, + "filepath": "src/components/combo_box/combo_box_options_list/combo_box_options_list.js" + }, + { + "token": "euiComboBoxOptionsList.allOptionsSelected", + "defString": "You've selected all available options", + "highlighting": "string", + "loc": { + "start": { + "line": 182, + "column": 10 + }, + "end": { + "line": 182, + "column": 118 + } + }, + "filepath": "src/components/combo_box/combo_box_options_list/combo_box_options_list.js" + }, + { + "token": "euiFormControlLayoutClearButton.label", + "defString": "Clear input", + "highlighting": "string", + "loc": { + "start": { + "line": 16, + "column": 4 + }, + "end": { + "line": 16, + "column": 81 + } + }, + "filepath": "src/components/form/form_control_layout/form_control_layout_clear_button.js" + }, + { + "token": "euiForm.addressFormErrors", + "defString": "Please address the errors in your form.", + "highlighting": "string", + "loc": { + "start": { + "line": 35, + "column": 6 + }, + "end": { + "line": 35, + "column": 99 + } + }, + "filepath": "src/components/form/form.js" + }, + { + "token": "euiSuperSelectControl.selectAnOption", + "defString": "Select an option: {selectedValue}, is selected", + "highlighting": "string", + "loc": { + "start": { + "line": 77, + "column": 12 + }, + "end": { + "line": 81, + "column": 14 + } + }, + "filepath": "src/components/form/super_select/super_select_control.js" + }, + { + "token": "euiSuperSelect.screenReaderAnnouncement", + "defString": "You are in a form selector of {optionsCount} items and must select a single option.\n Use the up and down keys to navigate or escape to close.", + "highlighting": "string", + "loc": { + "start": { + "line": 249, + "column": 12 + }, + "end": { + "line": 254, + "column": 14 + } + }, + "filepath": "src/components/form/super_select/super_select.js" + }, + { + "token": "euiHeaderAlert.dismiss", + "defString": "Dismiss", + "highlighting": "string", + "loc": { + "start": { + "line": 29, + "column": 4 + }, + "end": { + "line": 29, + "column": 62 + } + }, + "filepath": "src/components/header/header_alert/header_alert.js" + }, + { + "token": "euiHeaderLinks.openNavigationMenu", + "defString": "Open navigation menu", + "highlighting": "string", + "loc": { + "start": { + "line": 46, + "column": 8 + }, + "end": { + "line": 46, + "column": 90 + } + }, + "filepath": "src/components/header/header_links/header_links.js" + }, + { + "token": "euiHeaderLinks.appNavigation", + "defString": "App navigation", + "highlighting": "string", + "loc": { + "start": { + "line": 60, + "column": 6 + }, + "end": { + "line": 60, + "column": 77 + } + }, + "filepath": "src/components/header/header_links/header_links.js" + }, + { + "token": "euiModal.closeModal", + "defString": "Closes this modal window", + "highlighting": "string", + "loc": { + "start": { + "line": 64, + "column": 10 + }, + "end": { + "line": 64, + "column": 82 + } + }, + "filepath": "src/components/modal/modal.js" + }, + { + "token": "euiPagination.pageOfTotal", + "defString": "Page {page} of {total}", + "highlighting": "string", + "loc": { + "start": { + "line": 28, + "column": 6 + }, + "end": { + "line": 33, + "column": 7 + } + }, + "filepath": "src/components/pagination/pagination.js" + }, + { + "token": "euiPagination.previousPage", + "defString": "Previous page", + "highlighting": "string", + "loc": { + "start": { + "line": 51, + "column": 4 + }, + "end": { + "line": 51, + "column": 72 + } + }, + "filepath": "src/components/pagination/pagination.js" + }, + { + "token": "euiPagination.pageOfTotal", + "defString": "Page {page} of {total}", + "highlighting": "string", + "loc": { + "start": { + "line": 69, + "column": 6 + }, + "end": { + "line": 73, + "column": 7 + } + }, + "filepath": "src/components/pagination/pagination.js" + }, + { + "token": "euiPagination.jumpToLastPage", + "defString": "Jump to the last page, number {pageCount}", + "highlighting": "string", + "loc": { + "start": { + "line": 118, + "column": 6 + }, + "end": { + "line": 122, + "column": 7 + } + }, + "filepath": "src/components/pagination/pagination.js" + }, + { + "token": "euiPagination.nextPage", + "defString": "Next page", + "highlighting": "string", + "loc": { + "start": { + "line": 138, + "column": 4 + }, + "end": { + "line": 138, + "column": 64 + } + }, + "filepath": "src/components/pagination/pagination.js" + }, + { + "token": "euiPopover.screenReaderAnnouncement", + "defString": "You are in a popup. To exit this popup, hit escape.", + "highlighting": "string", + "loc": { + "start": { + "line": 435, + "column": 14 + }, + "end": { + "line": 435, + "column": 130 + } + }, + "filepath": "src/components/popover/popover.js" + }, + { + "token": "euiStepHorizontal.buttonTitle", + "defString": "({\n step,\n title,\n disabled,\n isComplete\n}) => {\n let titleAppendix = '';\n\n if (disabled) {\n titleAppendix = ' is disabled';\n } else if (isComplete) {\n titleAppendix = ' is complete';\n }\n\n return `Step ${step}: ${title}${titleAppendix}`;\n};", + "highlighting": "code", + "loc": { + "start": { + "line": 56, + "column": 4 + }, + "end": { + "line": 69, + "column": 5 + } + }, + "filepath": "src/components/steps/step_horizontal.js" + }, + { + "token": "euiStepHorizontal.step", + "defString": "Step", + "highlighting": "string", + "loc": { + "start": { + "line": 82, + "column": 38 + }, + "end": { + "line": 82, + "column": 94 + } + }, + "filepath": "src/components/steps/step_horizontal.js" + }, + { + "token": "euiStepNumber.isComplete", + "defString": "complete", + "highlighting": "string", + "loc": { + "start": { + "line": 42, + "column": 6 + }, + "end": { + "line": 42, + "column": 67 + } + }, + "filepath": "src/components/steps/step_number.js" + }, + { + "token": "euiStepNumber.hasWarnings", + "defString": "has warnings", + "highlighting": "string", + "loc": { + "start": { + "line": 48, + "column": 6 + }, + "end": { + "line": 48, + "column": 72 + } + }, + "filepath": "src/components/steps/step_number.js" + }, + { + "token": "euiStepNumber.hasErrors", + "defString": "has errors", + "highlighting": "string", + "loc": { + "start": { + "line": 54, + "column": 6 + }, + "end": { + "line": 54, + "column": 68 + } + }, + "filepath": "src/components/steps/step_number.js" + }, + { + "token": "euiStep.incompleteStep", + "defString": "Incomplete Step", + "highlighting": "string", + "loc": { + "start": { + "line": 35, + "column": 23 + }, + "end": { + "line": 35, + "column": 90 + } + }, + "filepath": "src/components/steps/step.js" + }, + { + "token": "euiStep.completeStep", + "defString": "Step", + "highlighting": "string", + "loc": { + "start": { + "line": 37, + "column": 23 + }, + "end": { + "line": 37, + "column": 77 + } + }, + "filepath": "src/components/steps/step.js" + }, + { + "token": "euiTableSortMobile.sorting", + "defString": "Sorting", + "highlighting": "string", + "loc": { + "start": { + "line": 59, + "column": 8 + }, + "end": { + "line": 59, + "column": 71 + } + }, + "filepath": "src/components/table/mobile/table_sort_mobile.js" + }, + { + "token": "euiTablePagination.rowsPerPage", + "defString": "Rows per page", + "highlighting": "string", + "loc": { + "start": { + "line": 53, + "column": 8 + }, + "end": { + "line": 53, + "column": 81 + } + }, + "filepath": "src/components/table/table_pagination/table_pagination.js" + }, + { + "token": "euiToast.dismissToast", + "defString": "Dismiss toast", + "highlighting": "string", + "loc": { + "start": { + "line": 49, + "column": 6 + }, + "end": { + "line": 49, + "column": 69 + } + }, + "filepath": "src/components/toast/toast.js" + }, + { + "token": "euiToast.newNotification", + "defString": "A new notification appears", + "highlighting": "string", + "loc": { + "start": { + "line": 86, + "column": 11 + }, + "end": { + "line": 86, + "column": 91 + } + }, + "filepath": "src/components/toast/toast.js" + }, + { + "token": "euiToast.notification", + "defString": "Notification", + "highlighting": "string", + "loc": { + "start": { + "line": 89, + "column": 6 + }, + "end": { + "line": 89, + "column": 68 + } + }, + "filepath": "src/components/toast/toast.js" + } +] \ No newline at end of file From e1b2baddf7d191ca1c3cd4da14f405fbeae761ec Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Wed, 6 Feb 2019 12:21:05 -0700 Subject: [PATCH 08/12] new package scripts --- package.json | 2 ++ src-docs/src/routes.js | 3 +++ src-docs/src/views/package/i18n_tokens.js | 14 ++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 src-docs/src/views/package/i18n_tokens.js diff --git a/package.json b/package.json index fadcf82cd17..d9b20537c61 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "test-docker": "docker pull $npm_package_docker_image && docker run --rm -i -e GIT_COMMITTER_NAME=test -e GIT_COMMITTER_EMAIL=test --user=$(id -u):$(id -g) -e HOME=/tmp -v $(pwd):/app -w /app $npm_package_docker_image bash -c 'npm config set spin false && /opt/yarn*/bin/yarn && npm run test && npm run build'", "sync-docs": "node ./scripts/docs-sync.js", "build-docs": "webpack --config=src-docs/webpack.config.js", + "prebuild": "yarn extract-i18n-strings", "build": "node ./scripts/compile-clean.js && node ./scripts/compile-eui.js && node ./scripts/compile-scss.js", + "extract-i18n-strings": "node ./scripts/babel/fetch-i18n-strings", "lint": "yarn lint-es && yarn lint-ts && yarn lint-sass && yarn lint-framer", "lint-fix": "yarn lint-es-fix && yarn lint-ts-fix", "lint-es": "eslint --cache --ignore-pattern \"**/*.snap.js\" \"src/**/*.js\" \"src-docs/**/*.js\"", diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index 6db4e77e6c6..7a7120bcaa1 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -264,6 +264,9 @@ import { XYChartLineExample } import { Changelog } from './views/package/changelog'; +import { I18nTokens } + from './views/package/i18n_tokens'; + import { SuperSelectExample } from './views/super_select/super_select_example'; diff --git a/src-docs/src/views/package/i18n_tokens.js b/src-docs/src/views/package/i18n_tokens.js new file mode 100644 index 00000000000..f5c87cc3753 --- /dev/null +++ b/src-docs/src/views/package/i18n_tokens.js @@ -0,0 +1,14 @@ +import React from 'react'; +import tokens from '../../i18ntokens'; + +import { EuiCodeBlock } from '../../../../src'; +import { GuidePage } from '../../components/guide_page'; + +export const I18nTokens = { + name: 'I18n Tokens', + component: () => ( + + {JSON.stringify(tokens, null, 2)} + + ), +}; From 5a31d20a409393e981277bc8c1d47674badbedb5 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Wed, 6 Feb 2019 13:56:05 -0700 Subject: [PATCH 09/12] List all I18n tokens on their own docs page --- src-docs/src/views/package/i18n_tokens.js | 41 +++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src-docs/src/views/package/i18n_tokens.js b/src-docs/src/views/package/i18n_tokens.js index f5c87cc3753..40f2c7ce201 100644 --- a/src-docs/src/views/package/i18n_tokens.js +++ b/src-docs/src/views/package/i18n_tokens.js @@ -1,14 +1,49 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import tokens from '../../i18ntokens'; -import { EuiCodeBlock } from '../../../../src'; +import { EuiCodeBlock, EuiInMemoryTable } from '../../../../src'; import { GuidePage } from '../../components/guide_page'; +const columns = [ + { name: 'Token', field: 'token' }, + { + name: 'Default', + render({ defString, highlighting }) { + return ( + {defString} + ); + } + }, + { + name: 'File', + render({ filepath, loc }) { + return ( + + {filepath}:{loc.start.line}:{loc.start.column} + + ); + }, + }, +]; + +const search = { + box: { + incremental: true, + schema: true + } +}; + export const I18nTokens = { name: 'I18n Tokens', component: () => ( - {JSON.stringify(tokens, null, 2)} + ), }; From 696ce16cf33857216778c140790c6aa9626314c0 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Wed, 6 Feb 2019 14:32:49 -0700 Subject: [PATCH 10/12] Pull i18n tokens out during build, validate --- package.json | 3 +-- scripts/babel/fetch-i18n-strings.js | 21 +++++++++++++++++++++ src-docs/src/i18ntokens.json | 4 ++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a8290053d6f..95e9b4384ca 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "test-docker": "docker pull $npm_package_docker_image && docker run --rm -i -e GIT_COMMITTER_NAME=test -e GIT_COMMITTER_EMAIL=test --user=$(id -u):$(id -g) -e HOME=/tmp -v $(pwd):/app -w /app $npm_package_docker_image bash -c 'npm config set spin false && /opt/yarn*/bin/yarn && npm run test && npm run build'", "sync-docs": "node ./scripts/docs-sync.js", "build-docs": "webpack --config=src-docs/webpack.config.js", - "prebuild": "yarn extract-i18n-strings", - "build": "node ./scripts/compile-clean.js && node ./scripts/compile-eui.js && node ./scripts/compile-scss.js", + "build": "yarn extract-i18n-strings && node ./scripts/compile-clean.js && node ./scripts/compile-eui.js && node ./scripts/compile-scss.js", "extract-i18n-strings": "node ./scripts/babel/fetch-i18n-strings", "lint": "yarn lint-es && yarn lint-ts && yarn lint-sass && yarn lint-framer", "lint-fix": "yarn lint-es-fix && yarn lint-ts-fix", diff --git a/scripts/babel/fetch-i18n-strings.js b/scripts/babel/fetch-i18n-strings.js index 27681942ec3..7977396e5e6 100644 --- a/scripts/babel/fetch-i18n-strings.js +++ b/scripts/babel/fetch-i18n-strings.js @@ -97,7 +97,28 @@ function traverseFile(filepath) { return true; }); + // extract tokens from source files files.forEach(filename => traverseFile(filename)); + + // validate tokens + tokenMappings.reduce( + (mappings, symbol) => { + const { token, defString } = symbol; + + if (mappings.hasOwnProperty(token)) { + if (mappings[token] !== defString) { + console.error(`Token ${token} has two differing defaults:\n${defString}\n${mappings[token]}`); + process.exit(1); + } + } else { + mappings[token] = defString; + } + + return mappings; + }, + {} + ); + fs.writeFileSync( join(rootDir, 'src-docs', 'src', 'i18ntokens.json'), JSON.stringify(tokenMappings, null, 2) diff --git a/src-docs/src/i18ntokens.json b/src-docs/src/i18ntokens.json index 8fab3e4ea14..0c5224c9c3c 100644 --- a/src-docs/src/i18ntokens.json +++ b/src-docs/src/i18ntokens.json @@ -473,7 +473,7 @@ "column": 6 }, "end": { - "line": 73, + "line": 74, "column": 7 } }, @@ -489,7 +489,7 @@ "column": 6 }, "end": { - "line": 122, + "line": 123, "column": 7 } }, From c6d7c3c589b3c9209c70cda83e58b00f97e77bce Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Thu, 7 Feb 2019 11:05:43 -0700 Subject: [PATCH 11/12] pr feedback --- scripts/babel/fetch-i18n-strings.js | 135 ++++++++++++++-------------- 1 file changed, 65 insertions(+), 70 deletions(-) diff --git a/scripts/babel/fetch-i18n-strings.js b/scripts/babel/fetch-i18n-strings.js index 7977396e5e6..62abc386ccd 100644 --- a/scripts/babel/fetch-i18n-strings.js +++ b/scripts/babel/fetch-i18n-strings.js @@ -4,7 +4,6 @@ const fs = require('fs'); const { promisify } = require('util'); const { basename, join, relative } = require('path'); const glob = require('glob'); -const asyncGlob = promisify(glob); const rootDir = join(__dirname, '..', '..'); const srcDir = join(rootDir, 'src'); @@ -20,40 +19,38 @@ function getCodeForExpression(expressionNode) { } function handleJSXPath(path) { - if (path.node.name.name === 'EuiI18n') { - const symbols = []; - - const attributes = path.node.attributes.reduce( - (attributes, node) => { - attributes[node.name.name] = node.value; - return attributes; - }, - {} - ); - - if (attributes.hasOwnProperty('token') && attributes.hasOwnProperty('default')) { - const tokenNode = attributes.token; - const defStringNode = attributes.default; - - let defString; - let highlighting; - if (defStringNode.type === 'StringLiteral') { - defString = defStringNode.value; - highlighting = 'string'; - } else if (defStringNode.type === 'JSXExpressionContainer') { - defString = getCodeForExpression(defStringNode.expression); - highlighting = 'code'; - } - symbols.push({ - token: tokenNode.value, - defString, - highlighting, - loc: path.node.loc - }); - } + const symbols = []; - return symbols; + const attributes = path.node.attributes.reduce( + (attributes, node) => { + attributes[node.name.name] = node.value; + return attributes; + }, + {} + ); + + if (attributes.hasOwnProperty('token') && attributes.hasOwnProperty('default')) { + const tokenNode = attributes.token; + const defStringNode = attributes.default; + + let defString; + let highlighting; + if (defStringNode.type === 'StringLiteral') { + defString = defStringNode.value; + highlighting = 'string'; + } else if (defStringNode.type === 'JSXExpressionContainer') { + defString = getCodeForExpression(defStringNode.expression); + highlighting = 'code'; + } + symbols.push({ + token: tokenNode.value, + defString, + highlighting, + loc: path.node.loc + }); } + + return symbols; } function traverseFile(filepath) { @@ -84,43 +81,41 @@ function traverseFile(filepath) { ); } -(async () => { - const files = (await asyncGlob( - '**/*.@(js|ts|tsx)', - { cwd: srcDir, realpath: true }, - )).filter(filepath => { - if (filepath.endsWith('index.d.ts')) return false; - if (filepath.endsWith('test.ts')) return false; - if (filepath.endsWith('test.tsx')) return false; - if (filepath.endsWith('test.js')) return false; - - return true; - }); - - // extract tokens from source files - files.forEach(filename => traverseFile(filename)); - - // validate tokens - tokenMappings.reduce( - (mappings, symbol) => { - const { token, defString } = symbol; - - if (mappings.hasOwnProperty(token)) { - if (mappings[token] !== defString) { - console.error(`Token ${token} has two differing defaults:\n${defString}\n${mappings[token]}`); - process.exit(1); - } - } else { - mappings[token] = defString; +const files = glob.sync( + '**/*.@(js|ts|tsx)', + { cwd: srcDir, realpath: true }, +).filter(filepath => { + if (filepath.endsWith('index.d.ts')) return false; + if (filepath.endsWith('test.ts')) return false; + if (filepath.endsWith('test.tsx')) return false; + if (filepath.endsWith('test.js')) return false; + + return true; +}); + +// extract tokens from source files +files.forEach(filename => traverseFile(filename)); + +// validate tokens +tokenMappings.reduce( + (mappings, symbol) => { + const { token, defString } = symbol; + + if (mappings.hasOwnProperty(token)) { + if (mappings[token] !== defString) { + console.error(`Token ${token} has two differing defaults:\n${defString}\n${mappings[token]}`); + process.exit(1); } + } else { + mappings[token] = defString; + } - return mappings; - }, - {} - ); + return mappings; + }, + {} +); - fs.writeFileSync( - join(rootDir, 'src-docs', 'src', 'i18ntokens.json'), - JSON.stringify(tokenMappings, null, 2) - ); -})(); +fs.writeFileSync( + join(rootDir, 'src-docs', 'src', 'i18ntokens.json'), + JSON.stringify(tokenMappings, null, 2) +); From dd6d9780ad2a8af150d4a76eeb2522e61f9f2c2c Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Fri, 8 Feb 2019 09:54:07 -0700 Subject: [PATCH 12/12] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64d77755643..fdff744dda0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Added pseudo-localization mode to docs ([#1541](https://github.com/elastic/eui/pull/1541)) +- New docs page listing localization tokens ([#1541](https://github.com/elastic/eui/pull/1541)) + **Bug fixes** - Fixed `EuiSearchBar.Query` match_all query string must be `*` ([#1521](https://github.com/elastic/eui/pull/1521))