Skip to content

Commit

Permalink
support automatic i18n string translation to pseudo code for debugging (
Browse files Browse the repository at this point in the history
#1512)

* support automatic i18n string translation to pseudo code for debugging

* support automatic i18n string translation to pseudo code for debugging

* added script to fetch all i18n strings from components

* added script to fetch all i18n strings from components

* Pseudo-translate I18n string placeholder values

* Update pseudo-translation toggle in docs

* write i18n token data to src-docs

* new package scripts

* List all I18n tokens on their own docs page

* Pull i18n tokens out during build, validate
  • Loading branch information
lizozom authored and chandlerprall committed Feb 6, 2019
1 parent d491d31 commit 17cab33
Show file tree
Hide file tree
Showing 24 changed files with 1,178 additions and 9 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"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",
"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",
"lint-es": "eslint --cache --ignore-pattern \"**/*.snap.js\" \"src/**/*.js\" \"src-docs/**/*.js\"",
Expand Down
126 changes: 126 additions & 0 deletions scripts/babel/fetch-i18n-strings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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 rootDir = join(__dirname, '..', '..');
const srcDir = join(rootDir, 'src');

const tokenMappings = [];

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') {
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
});
}

return symbols;
}
}

function traverseFile(filepath) {
const source = fs.readFileSync(filepath);
const ast = babel.parse(
source,
{
...babelOptions,
filename: basename(filepath),
ast: true
}
);

babel.traverse(
ast,
{
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) }
);
}
}
}
}
);
}

(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;
}

return mappings;
},
{}
);

fs.writeFileSync(
join(rootDir, 'src-docs', 'src', 'i18ntokens.json'),
JSON.stringify(tokenMappings, null, 2)
);
})();
3 changes: 3 additions & 0 deletions src-docs/src/actions/action_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ export default keyMirror({

// Theme actions
TOGGLE_THEME: null,

// Locale actions
TOGGLE_LOCALE: null,
});
4 changes: 4 additions & 0 deletions src-docs/src/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export {
toggleTheme,
} from './theme_actions';

export {
toggleLocale,
} from './locale_actions';
8 changes: 8 additions & 0 deletions src-docs/src/actions/locale_actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import ActionTypes from './action_types';

export const toggleLocale = locale => ({
type: ActionTypes.TOGGLE_LOCALE,
data: {
locale,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import {
EuiSwitch,
} 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 otherLocale = this.props.selectedLocale === 'en' ? 'en-xa' : 'en';

return (
<EuiSwitch
label="pseudo translation"
checked={this.props.selectedLocale === 'en-xa'}
onChange={() => this.props.onToggleLocale(otherLocale)}
compressed={true}
/>
);
}
}

GuideLocaleSelector.propTypes = {
onToggleLocale: PropTypes.func.isRequired,
selectedLocale: PropTypes.string.isRequired,
};
1 change: 1 addition & 0 deletions src-docs/src/components/guide_locale_selector/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GuideLocaleSelector } from './guide_locale_selector';
19 changes: 18 additions & 1 deletion src-docs/src/components/guide_page/guide_page_chrome.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
EuiText,
} from '../../../../src/components';

import {
GuideLocaleSelector,
} from '../guide_locale_selector';
import {
GuideThemeSelector,
} from '../guide_theme_selector';
Expand Down Expand Up @@ -78,7 +81,7 @@ export class GuidePageChrome extends Component {
);

return (
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap>
<EuiFlexItem grow={false}>
{homeLink}
</EuiFlexItem>
Expand All @@ -89,6 +92,18 @@ export class GuidePageChrome extends Component {
selectedTheme={this.props.selectedTheme}
/>
</EuiFlexItem>
{
location.host === 'localhost:8030' // eslint-disable-line no-restricted-globals
? (
<EuiFlexItem grow={false}>
<GuideLocaleSelector
onToggleLocale={this.props.onToggleLocale}
selectedLocale={this.props.selectedLocale}
/>
</EuiFlexItem>
)
: null
}
</EuiFlexGroup>
);
}
Expand Down Expand Up @@ -198,5 +213,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,
};
Loading

0 comments on commit 17cab33

Please sign in to comment.