├── register.js
├── .gitattributes
├── .eslintignore
├── .prettierrc
├── docs
└── screenshot.png
├── .babelrc
├── .eslintrc.json
├── src
├── constants.js
├── manager.js
├── preview.js
└── Tool.jsx
├── .editorconfig
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── rollup.config.js
├── package.json
└── README.md
/register.js:
--------------------------------------------------------------------------------
1 | require('./manager').register();
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.png binary
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /manager.js
4 | /preview.js
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/docs/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fynncfchen/storybook-addon-i18next/HEAD/docs/screenshot.png
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": ["@babel/plugin-proposal-object-rest-spread"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": ["airbnb", "prettier", "prettier/react"],
4 | "parser": "babel-eslint",
5 | "plugins": ["prettier"],
6 | "rules": {
7 | "prettier/prettier": ["error"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const ADDON_ID = 'i18next';
2 | export const PANEL_ID = `${ADDON_ID}/addon-panel`;
3 |
4 | export const LANGUAGE_CHANGED_EVENT_ID = 'addon:i18next:languageChanged';
5 | export const CONFIGURE_EVENT_ID = 'addon:i18next:configure';
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | end_of_line = lf
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS generated files
2 | .DS_Store
3 | .DS_Store?
4 | ._*
5 | .Spotlight-V100
6 | .Trashes
7 | ehthumbs.db
8 | Thumbs.db
9 |
10 | # Editor
11 | .vscode
12 |
13 | # Logs
14 | logs
15 | *.log
16 | npm-debug.log*
17 | yarn-error.log*
18 |
19 | # Distribution
20 | /manager.js
21 | /preview.js
22 |
23 | # Dependency directory
24 | node_modules
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [Unreleased]
4 |
5 | Changes that have landed in develop but are not yet released.
6 |
7 | ## v1.1.0 (Mar 8, 2019)
8 |
9 | ### Breaking Changes
10 |
11 | - Upgrade `react-i18next` to v10
12 | - Update example in README
13 |
14 | ## v1.0.0 (Mar 7, 2019)
15 |
16 | ### Breaking Changes
17 |
18 | - Upgrade Storybook to 5.0.0
19 |
--------------------------------------------------------------------------------
/src/manager.js:
--------------------------------------------------------------------------------
1 | /* eslint react/jsx-filename-extension:off */
2 | import React from 'react';
3 | import addons, { types } from '@storybook/addons';
4 |
5 | import Tool from './Tool';
6 |
7 | import { ADDON_ID } from './constants';
8 |
9 | // eslint-disable-next-line import/prefer-default-export
10 | export const register = () => {
11 | addons.register(ADDON_ID, api => {
12 | addons.add(ADDON_ID, {
13 | title: 'i18next / languages',
14 | type: types.TOOL,
15 | match: ({ viewMode }) => viewMode === 'story',
16 | render: () => ,
17 | });
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Chi-Feng Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import { eslint } from 'rollup-plugin-eslint';
3 | import resolve from 'rollup-plugin-node-resolve';
4 | import commonjs from 'rollup-plugin-commonjs';
5 |
6 | const plugins = [
7 | eslint(),
8 | babel({
9 | exclude: 'node_modules/**',
10 | }),
11 | resolve({
12 | extensions: ['.js', '.jsx'],
13 | customResolveOptions: {
14 | moduleDirectory: 'src',
15 | },
16 | }),
17 | commonjs(),
18 | ];
19 |
20 | export default [
21 | {
22 | input: './src/preview.js',
23 | output: [
24 | {
25 | file: './preview.js',
26 | format: 'cjs',
27 | },
28 | ],
29 | plugins,
30 | external: [
31 | '@storybook/addons',
32 | 'core-js/modules/es6.function.bind',
33 | 'prop-types',
34 | 'react',
35 | 'react-i18next',
36 | ],
37 | },
38 | {
39 | input: './src/manager.js',
40 | output: [
41 | {
42 | file: './manager.js',
43 | format: 'cjs',
44 | },
45 | ],
46 | plugins,
47 | external: [
48 | '@emotion/styled',
49 | '@storybook/addons',
50 | '@storybook/core-events',
51 | '@storybook/components',
52 | 'core-js',
53 | 'prop-types',
54 | 'react',
55 | ],
56 | },
57 | ];
58 |
--------------------------------------------------------------------------------
/src/preview.js:
--------------------------------------------------------------------------------
1 | /* eslint react/jsx-filename-extension:off */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import addons, { makeDecorator } from '@storybook/addons';
5 | import { I18nextProvider } from 'react-i18next';
6 |
7 | import { CONFIGURE_EVENT_ID, LANGUAGE_CHANGED_EVENT_ID } from './constants';
8 |
9 | class Wrapper extends React.Component {
10 | constructor(props, context) {
11 | super(props, context);
12 |
13 | this.changeLanguage = this.changeLanguage.bind(this);
14 | }
15 |
16 | componentDidMount() {
17 | const { channel } = this.props;
18 | channel.on(LANGUAGE_CHANGED_EVENT_ID, this.changeLanguage);
19 | }
20 |
21 | componentWillUnmount() {
22 | const { channel } = this.props;
23 | channel.removeListener(LANGUAGE_CHANGED_EVENT_ID, this.changeLanguage);
24 | }
25 |
26 | changeLanguage(language) {
27 | const { i18n } = this.props;
28 | i18n.changeLanguage(language);
29 | }
30 |
31 | render() {
32 | const { story, i18n } = this.props;
33 | return {story};
34 | }
35 | }
36 |
37 | Wrapper.propTypes = {
38 | story: PropTypes.node.isRequired,
39 | channel: PropTypes.shape({
40 | on: PropTypes.func,
41 | removeListener: PropTypes.func,
42 | }).isRequired,
43 | i18n: PropTypes.shape({
44 | changeLanguage: PropTypes.func,
45 | }).isRequired,
46 | };
47 |
48 | // eslint-disable-next-line import/prefer-default-export
49 | export const withI18next = makeDecorator({
50 | name: 'withI18next',
51 | wrapper: (getStory, context, { options }) => {
52 | const channel = addons.getChannel();
53 | const { i18n } = options;
54 | channel.emit(CONFIGURE_EVENT_ID, options);
55 | return ;
56 | },
57 | });
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "storybook-addon-i18next",
3 | "version": "1.3.1",
4 | "description": "React Storybook addon for i18next",
5 | "keywords": [
6 | "addon",
7 | "i18next",
8 | "storybook"
9 | ],
10 | "homepage": "https://github.com/fynncfchen/storybook-addon-i18next#readme",
11 | "bugs": {
12 | "url": "https://github.com/fynncfchen/storybook-addon-i18next/issues"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/fynncfchen/storybook-addon-i18next.git"
17 | },
18 | "license": "MIT",
19 | "files": [
20 | "src",
21 | "manager.js",
22 | "preview.js",
23 | "register.js"
24 | ],
25 | "main": "preview.js",
26 | "scripts": {
27 | "build": "rollup -c rollup.config.js",
28 | "clean": "rm -rf ./manager.js ./preview.js"
29 | },
30 | "dependencies": {
31 | "@emotion/styled": "^10.0.27",
32 | "@storybook/addons": "^5.3.17",
33 | "@storybook/components": "^6.5.15",
34 | "@storybook/core-events": "^5.3.17",
35 | "@storybook/theming": "^5.3.17",
36 | "prop-types": "^15.7.2",
37 | "react-i18next": "^11.3.3"
38 | },
39 | "devDependencies": {
40 | "@babel/core": "^7.8.7",
41 | "@babel/preset-env": "^7.8.7",
42 | "@babel/preset-react": "^7.8.3",
43 | "babel-eslint": "^10.1.0",
44 | "eslint": "^6.8.0",
45 | "eslint-config-airbnb": "^18.1.0",
46 | "eslint-config-prettier": "^6.10.0",
47 | "eslint-plugin-import": "^2.20.1",
48 | "eslint-plugin-jsx-a11y": "^6.2.3",
49 | "eslint-plugin-prettier": "^3.1.2",
50 | "eslint-plugin-react": "^7.19.0",
51 | "prettier": "^1.19.1",
52 | "rollup": "^2.0.6",
53 | "rollup-plugin-babel": "^4.4.0",
54 | "rollup-plugin-commonjs": "^10.1.0",
55 | "rollup-plugin-eslint": "^7.0.0",
56 | "rollup-plugin-node-resolve": "^5.2.0"
57 | },
58 | "peerDependencies": {
59 | "react": "*"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Storybook Addon i18next
2 |
3 | Storybook Addon i18next allows your stories to be displayed in
4 | different language with [i18next][i18next].
5 |
6 | NOTE: It only support React for now.
7 |
8 | 
9 |
10 | ## Installation
11 |
12 | Install the following npm module:
13 |
14 | ```sh
15 | npm i --save-dev storybook-addon-i18next
16 | ```
17 |
18 | or with yarn:
19 |
20 | ```sh
21 | yarn add -D storybook-addon-i18next
22 | ```
23 |
24 | Then, add following content to .storybook/addons.js
25 |
26 | ```js
27 | import 'storybook-addon-i18next/register';
28 | ```
29 |
30 | ## Decorator
31 |
32 | There's only one decorator for configuration.
33 |
34 | Import and use the `withI18next` decorator in your `config.js` file.
35 |
36 | ```js
37 | import { withI18next } from 'storybook-addon-i18next';
38 | ```
39 |
40 | ### i18n : Object
41 |
42 | ---
43 |
44 | An [configuration][i18next-configuration-options] object for [i18next][i18next].
45 |
46 | ### languages : Object
47 |
48 | ---
49 |
50 | A key-value pair of language codes and display name
51 |
52 | Example:
53 |
54 | ```javascript
55 | {
56 | en: 'English',
57 | 'zh-TW': '繁體中文',
58 | }
59 | ```
60 |
61 | ## Examples
62 |
63 | ### Basic Usage
64 |
65 | Simply import the Storybook i18next Addon in the `addons.js` file in your `.storybook` directory.
66 |
67 | ```js
68 | import 'storybook-addon-i18next/register';
69 | ```
70 |
71 | ### Add i18next Configuration
72 |
73 | Please refer to [i18next-configuration-options][i18next-configuration-options].
74 |
75 | Example in `.storybook/config.js`:
76 |
77 | ```javascript
78 | import i18n from 'i18next';
79 | import { initReactI18next } from 'react-i18next';
80 | import Backend from 'i18next-xhr-backend';
81 | import LanguageDetector from 'i18next-browser-languagedetector';
82 |
83 | i18n
84 | .use(Backend)
85 | .use(LanguageDetector)
86 | .use(initReactI18next)
87 | .init({
88 | whitelist: ['en', 'zh-TW'],
89 | lng: 'en',
90 | fallbackLng: 'en',
91 | interpolation: {
92 | escapeValue: false,
93 | },
94 | });
95 |
96 | addDecorator(
97 | withI18next({
98 | i18n,
99 | languages: {
100 | en: 'English',
101 | 'zh-TW': '繁體中文',
102 | },
103 | })
104 | );
105 |
106 | // Add after withI18next decorator
107 | addDecorator((story, context) => (
108 | {story(context)}
109 | ));
110 | ```
111 |
112 | [i18next]: https://www.i18next.com/
113 | [i18next-configuration-options]: https://www.i18next.com/overview/configuration-options
114 |
--------------------------------------------------------------------------------
/src/Tool.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import {
5 | Icons,
6 | IconButton,
7 | WithTooltip,
8 | TooltipLinkList,
9 | } from '@storybook/components';
10 | import { SET_STORIES } from '@storybook/core-events';
11 |
12 | import { CONFIGURE_EVENT_ID, LANGUAGE_CHANGED_EVENT_ID } from './constants';
13 |
14 | class I18NextTool extends React.Component {
15 | constructor(props) {
16 | super(props);
17 |
18 | this.state = {
19 | isTooltipExpanded: false,
20 | language: null,
21 | languages: null,
22 | };
23 |
24 | this.configure = this.configure.bind(this);
25 | this.emitLanguageChanged = this.emitLanguageChanged.bind(this);
26 | this.handleLanguageClick = this.handleLanguageClick.bind(this);
27 |
28 | this.listener = () => {
29 | const { api } = this.props;
30 | api.on(CONFIGURE_EVENT_ID, this.configure);
31 | };
32 | }
33 |
34 | componentDidMount() {
35 | const { api } = this.props;
36 | api.on(SET_STORIES, this.listener);
37 | }
38 |
39 | componentWillUnmount() {
40 | const { api } = this.props;
41 | api.off(SET_STORIES, this.listener);
42 | }
43 |
44 | configure(options) {
45 | const { i18n, languages } = options;
46 | const { language } = i18n;
47 | this.setState({ language, languages });
48 | }
49 |
50 | emitLanguageChanged() {
51 | const { api } = this.props;
52 | const { language } = this.state;
53 | api.emit(LANGUAGE_CHANGED_EVENT_ID, language);
54 | }
55 |
56 | handleLanguageClick(event) {
57 | const { dataset: { value } = {} } = event.currentTarget;
58 | this.setState({ isTooltipExpanded: false, language: value }, () => {
59 | this.emitLanguageChanged();
60 | });
61 | }
62 |
63 | render() {
64 | const { isTooltipExpanded, languages } = this.state;
65 |
66 | const items = Object.entries(languages || {}).map(([key, name]) => ({
67 | id: key,
68 | title: name,
69 | onClick: this.handleLanguageClick,
70 | 'data-value': key,
71 | }));
72 |
73 | return (
74 | this.setState({ isTooltipExpanded: t })}
79 | tooltip={}
80 | closeOnClick
81 | >
82 |
83 |
84 |
85 |
86 | );
87 | }
88 | }
89 |
90 | I18NextTool.propTypes = {
91 | api: PropTypes.shape({
92 | on: PropTypes.func,
93 | off: PropTypes.func,
94 | emit: PropTypes.func,
95 | }).isRequired,
96 | };
97 |
98 | export default I18NextTool;
99 |
--------------------------------------------------------------------------------