Yuki Takei 7 лет назад
Родитель
Сommit
0403d2cfe9

+ 8 - 0
config/webpack.common.js

@@ -45,6 +45,7 @@ module.exports = function(options) {
       alias: {
         '@root': helpers.root('/'),
         '@alias/logger': helpers.root('lib/service/logger'),
+        '@alias/locales': helpers.root('lib/locales'),
         // replace bunyan
         'bunyan': 'browser-bunyan',
       }
@@ -67,6 +68,13 @@ module.exports = function(options) {
             }
           }]
         },
+        {
+          test: /locales/,
+          loader: '@alienfast/i18next-loader',
+          options: {
+            basenameAsNamespace: true,
+          }
+        },
         {
           test: /\.css$/,
           use: ['style-loader', 'css-loader'],

+ 2 - 0
lib/locales/index.js

@@ -0,0 +1,2 @@
+// !!DO NOT REMOVE THIS FILE!!
+// entry point for @alienfast/i18next-loader

+ 1 - 0
lib/views/layout/layout.html

@@ -122,6 +122,7 @@ gh/highlightjs/cdn-release@9.12.0/build/languages/yaml.min.js
   {% block html_base_attr %}{% endblock %}
   data-csrftoken="{{ csrf() }}"
   data-current-username="{% if user %}{{ user.username }}{% endif %}"
+  data-userlang="{% if user %}{{ user.lang }}{% endif %}"
  >
 
 <div id="wrapper">

+ 3 - 0
package.json

@@ -97,6 +97,7 @@
     "passport-local": "^1.0.0",
     "react": "^16.2.0",
     "react-dom": "^16.2.0",
+    "react-i18next": "^7.6.1",
     "rimraf": "^2.6.1",
     "slack-node": "^0.1.8",
     "socket.io": "^2.0.3",
@@ -106,6 +107,7 @@
     "xss": "^0.3.5"
   },
   "devDependencies": {
+    "@alienfast/i18next-loader": "^1.0.16",
     "assets-webpack-plugin": "~3.5.1",
     "autoprefixer": "^8.2.0",
     "babel-core": "^6.25.0",
@@ -134,6 +136,7 @@
     "eslint-plugin-react": "^7.7.0",
     "extract-text-webpack-plugin": "^3.0.2",
     "file-loader": "^1.1.0",
+    "i18next-browser-languagedetector": "^2.2.0",
     "jquery-slimscroll": "^1.3.8",
     "jquery-ui": "^1.12.1",
     "jquery.cookie": "~1.4.1",

+ 11 - 3
resource/js/app.js

@@ -1,5 +1,8 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
+import { I18nextProvider } from 'react-i18next';
+
+import i18nFactory from './i18n';
 
 import Crowi from './util/Crowi';
 // import CrowiRenderer from './util/CrowiRenderer';
@@ -33,6 +36,9 @@ if (!window) {
   window = {};
 }
 
+const userlang = $('body').data('userlang');
+const i18n = i18nFactory(userlang);
+
 const mainContent = document.querySelector('#content-main');
 let pageId = null;
 let pageRevisionId = null;
@@ -209,9 +215,11 @@ if (pageEditorGrantSelectorElem) {
     pageGrant.grantGroup = grantGroup;
   }
   ReactDOM.render(
-    <GrantSelector crowi={crowi}
-      userRelatedGroups={userRelatedGroups} pageGrant={pageGrant}
-      onChange={updatePageGrantElems} />,
+    <I18nextProvider i18n={i18n}>
+      <GrantSelector crowi={crowi}
+        userRelatedGroups={userRelatedGroups} pageGrant={pageGrant}
+        onChange={updatePageGrantElems} />
+    </I18nextProvider>,
     pageEditorGrantSelectorElem
   );
 }

+ 9 - 4
resource/js/components/PageEditor/GrantSelector.js

@@ -1,9 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { translate } from 'react-i18next';
 
 import FormGroup from 'react-bootstrap/es/FormGroup';
 import FormControl from 'react-bootstrap/es/FormControl';
-import ControlLabel from 'react-bootstrap/es/ControlLabel';
+// import ControlLabel from 'react-bootstrap/es/ControlLabel';
 // import Button from 'react-bootstrap/es/Button';
 
 // import Modal from 'react-bootstrap/es/Modal';
@@ -15,7 +16,7 @@ import ControlLabel from 'react-bootstrap/es/ControlLabel';
  * @class GrantSelector
  * @extends {React.Component}
  */
-export default class GrantSelector extends React.Component {
+class GrantSelector extends React.Component {
 
   constructor(props) {
     super(props);
@@ -29,7 +30,7 @@ export default class GrantSelector extends React.Component {
 
     this.availableGrantLabels = {
       1: 'Public',
-      2: 'Anyone with the linc',
+      2: 'Anyone with the link',
       // 3:'Specified users only',
       4: 'Just me',
       5: 'Only inside the group',
@@ -97,8 +98,9 @@ export default class GrantSelector extends React.Component {
    * @memberof GrantSelector
    */
   renderGrantSelector() {
+    const { t } = this.props;
     const grantElems = this.availableGrants.map((grant) => {
-      return <option key={grant} value={grant}>{this.availableGrantLabels[grant]}</option>;
+      return <option key={grant} value={grant}>{t(this.availableGrantLabels[grant])}</option>;
     });
 
     const bsClassName = 'form-control-dummy'; // set form-control* to shrink width
@@ -171,9 +173,12 @@ export class UserGroup {
 }
 
 GrantSelector.propTypes = {
+  t: PropTypes.func.isRequired,               // i18next
   crowi: PropTypes.object.isRequired,
   isGroupModalShown: PropTypes.bool,
   userRelatedGroups: PropTypes.object,
   pageGrant: PropTypes.instanceOf(PageGrant),
   onChange: PropTypes.func,
 };
+
+export default translate()(GrantSelector);

+ 44 - 0
resource/js/i18n.js

@@ -0,0 +1,44 @@
+import i18n from 'i18next';
+import LanguageDetector from 'i18next-browser-languagedetector';
+import { reactI18nextModule } from 'react-i18next';
+
+import resources from '@alias/locales';
+
+export default (userlang) => {
+  // setup LanguageDetector
+  const langDetector = new LanguageDetector();
+  langDetector.addDetector({
+    name: 'userSettingDetector',
+    lookup(options) {
+      return userlang;
+    },
+    cacheUserlanguage(lng, options) {
+    },
+  });
+
+  return i18n
+    .use(langDetector)
+    .use(reactI18nextModule) // if not using I18nextProvider
+    .init({
+      debug: (process.env.NODE_ENV !== 'production'),
+      resources,
+      load: 'currentOnly',
+
+      fallbackLng: 'en-US',
+      detection: {
+        order: ['userSettingDetector', 'querystring', 'localStorage'],
+      },
+
+      interpolation: {
+        escapeValue: false, // not needed for react!!
+      },
+
+      // react i18next special options (optional)
+      react: {
+        wait: false,
+        bindI18n: 'languageChanged loaded',
+        bindStore: 'added removed',
+        nsMode: 'default'
+      }
+    });
+};

+ 53 - 1
yarn.lock

@@ -2,6 +2,15 @@
 # yarn lockfile v1
 
 
+"@alienfast/i18next-loader@^1.0.16":
+  version "1.0.16"
+  resolved "https://registry.yarnpkg.com/@alienfast/i18next-loader/-/i18next-loader-1.0.16.tgz#218e9736c94457d36bdae68a5085ed68434c9bbf"
+  dependencies:
+    glob-all "^3.1.0"
+    js-yaml "^3.11.0"
+    loader-utils "^1.1.0"
+    lodash "^4.17.10"
+
 "@browser-bunyan/console-formatted-stream@^1.3.0":
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/@browser-bunyan/console-formatted-stream/-/console-formatted-stream-1.3.0.tgz#3dc059aa5c1b2a7a1f26e2706e2bdeb9a09bbe57"
@@ -3130,6 +3139,13 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
+glob-all@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.1.0.tgz#8913ddfb5ee1ac7812656241b03d5217c64b02ab"
+  dependencies:
+    glob "^7.0.5"
+    yargs "~1.2.6"
+
 glob-base@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -3396,6 +3412,10 @@ hogan.js@^3.0.2:
     mkdirp "0.3.0"
     nopt "1.0.10"
 
+hoist-non-react-statics@^2.3.1:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
+
 home-or-tmp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -3411,6 +3431,12 @@ html-comment-regex@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
 
+html-parse-stringify2@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
+  dependencies:
+    void-elements "^2.0.1"
+
 http-errors@1.6.2, http-errors@~1.6.1, http-errors@~1.6.2:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
@@ -3461,6 +3487,10 @@ humanize-ms@^1.2.1:
   dependencies:
     ms "^2.0.0"
 
+i18next-browser-languagedetector@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.0.tgz#5f41abe61964a56dce70102ab31c3ed5d5866edc"
+
 i18next-express-middleware@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/i18next-express-middleware/-/i18next-express-middleware-1.1.1.tgz#9204f28c8800ac3bff87fbee01945367956f349c"
@@ -3853,7 +3883,7 @@ js-yaml@3.5.4:
     argparse "^1.0.2"
     esprima "^2.6.0"
 
-js-yaml@^3.4.3, js-yaml@^3.9.1:
+js-yaml@^3.11.0, js-yaml@^3.4.3, js-yaml@^3.9.1:
   version "3.11.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
   dependencies:
@@ -4614,6 +4644,10 @@ minimist@0.0.8:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
 
+minimist@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
+
 minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -5979,6 +6013,14 @@ react-dropzone@^4.2.7:
     attr-accept "^1.0.3"
     prop-types "^15.5.7"
 
+react-i18next@^7.6.1:
+  version "7.6.1"
+  resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-7.6.1.tgz#c61d8284f3c695893d51033f67c39e65f01212b6"
+  dependencies:
+    hoist-non-react-statics "^2.3.1"
+    html-parse-stringify2 "2.0.1"
+    prop-types "^15.6.0"
+
 react-onclickoutside@^6.1.1:
   version "6.7.0"
   resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.7.0.tgz#997a4d533114c9a0a104913638aa26afc084f75c"
@@ -7423,6 +7465,10 @@ vm-browserify@0.0.4:
   dependencies:
     indexof "0.0.1"
 
+void-elements@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+
 warning@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
@@ -7755,6 +7801,12 @@ yargs@^8.0.2:
     y18n "^3.2.1"
     yargs-parser "^7.0.0"
 
+yargs@~1.2.6:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-1.2.6.tgz#9c7b4a82fd5d595b2bf17ab6dcc43135432fe34b"
+  dependencies:
+    minimist "^0.1.0"
+
 yargs@~3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"