├── 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 | ![Screenshot](https://github.com/fynncfchen/storybook-addon-i18next/blob/master/docs/screenshot.png) 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 | --------------------------------------------------------------------------------