Jelajahi Sumber

Merge pull request #143 from crowi/support-i18n

Add I18N Support
Sotaro KARASAWA 9 tahun lalu
induk
melakukan
d3fcce2254

+ 29 - 0
lib/crowi/express-init.js

@@ -11,10 +11,37 @@ module.exports = function(crowi, app) {
     , flash          = require('connect-flash')
     , cons           = require('consolidate')
     , swig           = require('swig')
+    , i18next        = require('i18next')
+    , i18nFsBackend  = require('i18next-node-fs-backend')
+    , i18nSprintf    = require('i18next-sprintf-postprocessor')
+    , i18nMiddleware = require('i18next-express-middleware')
+    , i18nUserSettingDetector  = require('../util/i18nUserSettingDetector')
     , env            = crowi.node_env
     , middleware     = require('../util/middlewares')
+
+    , User = crowi.model('User')
     ;
 
+  var lngDetector = new i18nMiddleware.LanguageDetector();
+  lngDetector.addDetector(i18nUserSettingDetector);
+
+  i18next
+    .use(lngDetector)
+    .use(i18nFsBackend)
+    .use(i18nSprintf)
+    .init({
+      debug: (crowi.node_env === 'development'),
+      fallbackLng: [User.LANG_EN_US],
+      whitelist: Object.keys(User.getLanguageLabels()).map((k) => User[k]),
+      backend: {
+        loadPath: 'locales/{{lng}}/translation.json'
+      },
+      detection: {
+        order: ['userSettingDetector', 'header', 'navigator'],
+      },
+      overloadTranslationOptionHandler: i18nSprintf.overloadTranslationOptionHandler
+    });
+
   app.use(function(req, res, next) {
     var now = new Date()
       , baseUrl
@@ -41,6 +68,7 @@ module.exports = function(crowi, app) {
     res.locals.consts   = {
         pageGrants: Page.getGrantLabels(),
         userStatus: User.getUserStatusLabels(),
+        language:   User.getLanguageLabels(),
         registrationMode: Config.getRegistrationModeLabels(),
     };
 
@@ -78,4 +106,5 @@ module.exports = function(crowi, app) {
 
   app.use(middleware.loginChecker(crowi, app));
 
+  app.use(i18nMiddleware.handle(i18next));
 };

+ 1 - 0
lib/crowi/index.js

@@ -23,6 +23,7 @@ function Crowi (rootdir, env)
   this.publicDir = path.join(this.rootDir, 'public') + sep;
   this.libDir    = path.join(this.rootDir, 'lib') + sep;
   this.eventsDir = path.join(this.libDir, 'events') + sep;
+  this.localeDir = path.join(this.rootDir, 'locales') + sep;
   this.resourceDir = path.join(this.rootDir, 'resource') + sep;
   this.viewsDir  = path.join(this.libDir, 'views') + sep;
   this.mailDir   = path.join(this.viewsDir, 'mail') + sep;

+ 2 - 1
lib/form/me/user.js

@@ -5,5 +5,6 @@ var form = require('express-form')
 
 module.exports = form(
   field('userForm.name').trim().required(),
-  field('userForm.email').trim().isEmail().required()
+  field('userForm.email').trim().isEmail().required(),
+  field('userForm.lang').required()
 );

+ 5 - 5
lib/models/page.js

@@ -329,10 +329,10 @@ module.exports = function(crowi) {
 
   pageSchema.statics.getGrantLabels = function() {
     var grantLabels = {};
-    grantLabels[GRANT_PUBLIC]     = '公開';
-    grantLabels[GRANT_RESTRICTED] = 'リンクを知っている人のみ';
-    //grantLabels[GRANT_SPECIFIED]  = '特定ユーザーのみ';
-    grantLabels[GRANT_OWNER]      = '自分のみ';
+    grantLabels[GRANT_PUBLIC]     = 'Public'; // 公開
+    grantLabels[GRANT_RESTRICTED] = 'Anyone with the link'; // リンクを知っている人のみ
+    //grantLabels[GRANT_SPECIFIED]  = 'Specified users only'; // 特定ユーザーのみ
+    grantLabels[GRANT_OWNER]      = 'Just me'; // 自分のみ
 
     return grantLabels;
   };
@@ -579,7 +579,7 @@ module.exports = function(crowi) {
         {path: 'revision', model: 'Revision'},
       ])
       .sort({updatedAt: -1})
-      .stream();
+      .cursor();
   };
 
   /**

+ 38 - 9
lib/models/user.js

@@ -11,7 +11,12 @@ module.exports = function(crowi) {
     , STATUS_SUSPENDED  = 3
     , STATUS_DELETED    = 4
     , STATUS_INVITED    = 5
-    , USER_PUBLIC_FIELDS = '_id image googleId name username email introduction status createdAt admin' // TODO: どこか別の場所へ...
+    , USER_PUBLIC_FIELDS = '_id image googleId name username email introduction status lang createdAt admin' // TODO: どこか別の場所へ...
+
+    , LANG_EN    = 'en'
+    , LANG_EN_US = 'en-US'
+    , LANG_EN_GB = 'en-GB'
+    , LANG_JA    = 'ja'
 
     , PAGE_ITEMS        = 50
 
@@ -29,6 +34,11 @@ module.exports = function(crowi) {
     introduction: { type: String },
     password: String,
     apiToken: String,
+    lang: {
+      type: String,
+      enum: Object.keys(getLanguageLabels()).map((k) => eval(k)),
+      default: LANG_EN_US
+    },
     status: { type: Number, required: true, default: STATUS_ACTIVE, index: true  },
     createdAt: { type: Date, default: Date.now },
     admin: { type: Boolean, default: 0, index: true  }
@@ -84,6 +94,16 @@ module.exports = function(crowi) {
     return hasher.digest('base64');
   }
 
+  function getLanguageLabels() {
+    var lang = {};
+    lang.LANG_EN    = LANG_EN;
+    lang.LANG_EN_US = LANG_EN_US;
+    lang.LANG_EN_GB = LANG_EN_GB;
+    lang.LANG_JA    = LANG_JA;
+
+    return lang;
+  }
+
   userSchema.methods.isPasswordSet = function() {
     if (this.password) {
       return true;
@@ -107,9 +127,11 @@ module.exports = function(crowi) {
     return false;
   };
 
-  userSchema.methods.update = function(name, email, callback) {
+  userSchema.methods.update = function(name, email, lang, callback) {
     this.name = name;
     this.email = email;
+    this.lang = lang;
+
     this.save(function(err, userData) {
       return callback(err, userData);
     });
@@ -232,6 +254,7 @@ module.exports = function(crowi) {
     });
   };
 
+  userSchema.statics.getLanguageLabels = getLanguageLabels;
   userSchema.statics.getUserStatusLabels = function() {
     var userStatus = {};
     userStatus[STATUS_REGISTERED] = '承認待ち';
@@ -612,7 +635,7 @@ module.exports = function(crowi) {
     );
   };
 
-  userSchema.statics.createUserByEmailAndPassword = function(name, username, email, password, callback) {
+  userSchema.statics.createUserByEmailAndPassword = function(name, username, email, password, lang, callback) {
     var User = this
       , newUser = new User();
 
@@ -620,6 +643,7 @@ module.exports = function(crowi) {
     newUser.username = username;
     newUser.email = email;
     newUser.setPassword(password);
+    newUser.lang = lang;
     newUser.createdAt = Date.now();
     newUser.status = decideUserStatusOnRegistration();
 
@@ -647,13 +671,18 @@ module.exports = function(crowi) {
   };
 
 
-  userSchema.statics.STATUS_REGISTERED = STATUS_REGISTERED;
-  userSchema.statics.STATUS_ACTIVE = STATUS_ACTIVE;
-  userSchema.statics.STATUS_SUSPENDED = STATUS_SUSPENDED;
-  userSchema.statics.STATUS_DELETED = STATUS_DELETED;
-  userSchema.statics.STATUS_INVITED = STATUS_INVITED;
+  userSchema.statics.STATUS_REGISTERED  = STATUS_REGISTERED;
+  userSchema.statics.STATUS_ACTIVE      = STATUS_ACTIVE;
+  userSchema.statics.STATUS_SUSPENDED   = STATUS_SUSPENDED;
+  userSchema.statics.STATUS_DELETED     = STATUS_DELETED;
+  userSchema.statics.STATUS_INVITED     = STATUS_INVITED;
   userSchema.statics.USER_PUBLIC_FIELDS = USER_PUBLIC_FIELDS;
-  userSchema.statics.PAGE_ITEMS = PAGE_ITEMS;
+  userSchema.statics.PAGE_ITEMS         = PAGE_ITEMS;
+
+  userSchema.statics.LANG_EN            = LANG_EN;
+  userSchema.statics.LANG_EN_US         = LANG_EN_US;
+  userSchema.statics.LANG_EN_GB         = LANG_EN_US;
+  userSchema.statics.LANG_JA            = LANG_JA;
 
   return mongoose.model('User', userSchema);
 };

+ 2 - 1
lib/routes/installer.js

@@ -14,6 +14,7 @@ module.exports = function(crowi, app) {
 
   actions.createAdmin = function(req, res) {
     var registerForm = req.body.registerForm || {};
+    var language = req.language || 'en';
 
     if (req.form.isValid) {
       var name = registerForm.name;
@@ -21,7 +22,7 @@ module.exports = function(crowi, app) {
       var email = registerForm.email;
       var password = registerForm.password;
 
-      User.createUserByEmailAndPassword(name, username, email, password, function(err, userData) {
+      User.createUserByEmailAndPassword(name, username, email, password, language, function(err, userData) {
         if (err) {
           req.form.errors.push('管理ユーザーの作成に失敗しました。' + err.message);
           // TODO

+ 10 - 9
lib/routes/login.js

@@ -39,7 +39,7 @@ module.exports = function(crowi, app) {
   };
 
   var loginFailure = function(req, res) {
-    req.flash('warningMessage', 'ログインに失敗しました');
+    req.flash('warningMessage', 'Sign in failure.');
     return res.redirect('/login');
   };
 
@@ -59,9 +59,9 @@ module.exports = function(crowi, app) {
       ;
 
     if (reason === 'suspended') {
-      reasonMessage = 'このアカウントは停止されています。';
+      reasonMessage = 'This account is suspended.';
     } else if (reason === 'registered') {
-      reasonMessage = '管理者の承認をお待ちください。';
+      reasonMessage = 'Wait for approved by administrators.';
     } else {
     }
 
@@ -129,6 +129,7 @@ module.exports = function(crowi, app) {
 
   actions.register = function(req, res) {
     var googleAuth = require('../util/googleAuth')(config);
+    var lang= req.lang || User.LANG_EN_US;
 
     // ログイン済みならさようなら
     if (req.user) {
@@ -155,16 +156,16 @@ module.exports = function(crowi, app) {
         var isError = false;
         if (!User.isEmailValid(email)) {
           isError = true;
-          req.flash('registerWarningMessage', 'このメールアドレスは登録できません。(ホワイトリストなどを確認してください)');
+          req.flash('registerWarningMessage', 'This email address could not be used. (Make sure the allowed email address)');
         }
         if (!isRegisterable) {
           if (!errOn.username) {
             isError = true;
-            req.flash('registerWarningMessage', 'このユーザーIDは利用できません。');
+            req.flash('registerWarningMessage', 'This User ID is not available.');
           }
           if (!errOn.email) {
             isError = true;
-            req.flash('registerWarningMessage', 'このメールアドレスは登録済みです。');
+            req.flash('registerWarningMessage', 'This email address is already registered.');
           }
 
         }
@@ -173,9 +174,9 @@ module.exports = function(crowi, app) {
           return res.redirect('/register');
         }
 
-        User.createUserByEmailAndPassword(name, username, email, password, function(err, userData) {
+        User.createUserByEmailAndPassword(name, username, email, password, lang, function(err, userData) {
           if (err) {
-            req.flash('registerWarningMessage', 'ユーザー登録に失敗しました。');
+            req.flash('registerWarningMessage', 'Failed to register.');
             return res.redirect('/register');
           } else {
 
@@ -272,7 +273,7 @@ module.exports = function(crowi, app) {
           req.session.googleAuthCode = null;
 
           if (err) {
-            req.flash('registerWarningMessage', 'Googleコネクト中にエラーが発生しました。');
+            req.flash('registerWarningMessage', 'Error on connectiong Google');
             return res.redirect('/login?register=1'); // TODO Handling
           }
 

+ 15 - 13
lib/routes/me.js

@@ -82,13 +82,14 @@ module.exports = function(crowi, app) {
     if (req.method == 'POST' && req.form.isValid) {
       var name = userForm.name;
       var email = userForm.email;
+      var lang= userForm.lang;
 
       if (!User.isEmailValid(email)) {
-        req.form.errors.push('このメールアドレスは登録できません。(ホワイトリストなどを確認してください)');
+        req.form.errors.push('You can\'t update to that email address');
         return res.render('me/index', {});
       }
 
-      userData.update(name, email, function(err, userData) {
+      userData.update(name, email, lang, function(err, userData) {
         if (err) {
           for (var e in err.errors) {
             if (err.errors.hasOwnProperty(e)) {
@@ -98,7 +99,8 @@ module.exports = function(crowi, app) {
           return res.render('me/index', {});
         }
 
-        req.flash('successMessage', '更新しました');
+        req.i18n.changeLanguage(lang);
+        req.flash('successMessage', req.t('Updated'));
         return res.redirect('/me');
       });
     } else { // method GET
@@ -128,14 +130,14 @@ module.exports = function(crowi, app) {
       var oldPassword = passwordForm.oldPassword;
 
       if (userData.isPasswordSet() && !userData.isPasswordValid(oldPassword)) {
-        req.form.errors.push('現在のパスワードが違います。');
+        req.form.errors.push('Wrong current password');
         return res.render('me/password', {
         });
       }
 
       // check password confirm
       if (newPassword != newPasswordConfirm) {
-        req.form.errors.push('確認用パスワードが一致しません');
+        req.form.errors.push('Failed to verify passwords');
       } else {
         userData.updatePassword(newPassword, function(err, userData) {
           if (err) {
@@ -147,7 +149,7 @@ module.exports = function(crowi, app) {
             return res.render('me/password', {});
           }
 
-          req.flash('successMessage', 'パスワードを変更しました');
+          req.flash('successMessage', 'Password updated');
           return res.redirect('/me/password');
         });
       }
@@ -164,12 +166,12 @@ module.exports = function(crowi, app) {
     if (req.method == 'POST' && req.form.isValid) {
       userData.updateApiToken()
       .then(function(userData) {
-          req.flash('successMessage', 'API Token を更新しました');
+          req.flash('successMessage', 'API Token updated');
           return res.redirect('/me/apiToken');
       })
       .catch(function(err) {
           //req.flash('successMessage',);
-          req.form.errors.push('API Token の更新に失敗しました');
+          req.form.errors.push('Failed to update API Token');
           return res.render('me/api_token', {
           });
       });
@@ -187,7 +189,7 @@ module.exports = function(crowi, app) {
   actions.deletePicture = function(req, res) {
     // TODO: S3 からの削除
     req.user.deleteImage(function(err, data) {
-      req.flash('successMessage', 'プロフィール画像を削除しました');
+      req.flash('successMessage', 'Deleted profile picture');
       res.redirect('/me');
     });
   };
@@ -201,7 +203,7 @@ module.exports = function(crowi, app) {
     var toConnect = req.body.connectGoogle ? true : false;
     if (toDisconnect) {
       userData.deleteGoogleId(function(err, userData) {
-        req.flash('successMessage', 'Googleコネクトを解除しました。');
+        req.flash('successMessage', 'Disconnected from Google account');
 
         return res.redirect('/me');
       });
@@ -232,13 +234,13 @@ module.exports = function(crowi, app) {
       var googleId = tokenInfo.user_id;
       var googleEmail = tokenInfo.email;
       if (!User.isEmailValid(googleEmail)) {
-        req.flash('warningMessage.auth.google', 'このメールアドレスのGoogleアカウントはコネクトできません。');
+        req.flash('warningMessage.auth.google', 'You can\'t connect with this  Google\'s account');
         return res.redirect('/me');
       }
 
       User.findUserByGoogleId(googleId, function(err, googleUser) {
         if (!err && googleUser) {
-          req.flash('warningMessage.auth.google', 'このGoogleアカウントは他のユーザーがコネクト済みです。');
+          req.flash('warningMessage.auth.google', 'This Google\'s account is connected by another user');
           return res.redirect('/me');
         } else {
           userData.updateGoogleId(googleId, function(err, userData) {
@@ -249,7 +251,7 @@ module.exports = function(crowi, app) {
             }
 
             // TODO if err
-            req.flash('successMessage', 'Googleコネクトを設定しました。');
+            req.flash('successMessage', 'Connected with Google');
             return res.redirect('/me');
           });
         }

+ 1 - 1
lib/routes/page.js

@@ -844,7 +844,7 @@ module.exports = function(crowi, app) {
 
         return res.json(ApiResponse.success(result));
       }).catch(function(err) {
-        return res.json(ApiResponse.error('エラーが発生しました。ページを更新できません。'));
+        return res.json(ApiResponse.error('Failed to update page.'));
       });
     });
   };

+ 19 - 0
lib/util/i18nUserSettingDetector.js

@@ -0,0 +1,19 @@
+module.exports = {
+  name: 'userSettingDetector',
+
+  lookup: function(req, res, options) {
+    var lang = null;
+
+    if (req.user) {
+      if ('lang' in req.user) {
+        lang = req.user.lang || null;
+      }
+    }
+
+    return lang;
+  },
+
+  cacheUserlanguage: function(req, res, lng, options) {
+    // nothing to do
+  }
+};

+ 1 - 1
lib/util/middlewares.js

@@ -189,7 +189,7 @@ exports.loginRequired = function(crowi, app) {
 
 exports.accessTokenParser = function(crowi, app) {
   return function(req, res, next) {
-    var accessToken = req.query.access_token || req.body.access_token || null;
+    var accessToken = req.query.access_token || req.body.access_token || req.get('Authorization') || null;
     if (!accessToken) {
       return next();
     }

+ 3 - 3
lib/util/search.js

@@ -208,11 +208,11 @@ SearchClient.prototype.addAllPages = function()
   var self = this;
   var offset = 0;
   var Page = this.crowi.model('Page');
-  var stream = Page.getStreamOfFindAll();
+  var cursor = Page.getStreamOfFindAll();
   var body = [];
 
   return new Promise(function(resolve, reject) {
-    stream.on('data', function (doc) {
+    cursor.on('data', function (doc) {
       if (!doc.creator || !doc.revision || !self.shouldIndexed(doc)) {
         debug('Skipped', doc.path);
         return ;
@@ -221,7 +221,7 @@ SearchClient.prototype.addAllPages = function()
       self.prepareBodyForCreate(body, doc);
     }).on('error', function (err) {
       // TODO: handle err
-      debug('Error stream:', err);
+      debug('Error cursor:', err);
     }).on('close', function () {
       // all done
 

+ 2 - 2
lib/views/_form.html

@@ -45,12 +45,12 @@
         {% else %}
         <select name="pageForm[grant]" class="form-control">
           {% for grantId, grantLabel in consts.pageGrants %}
-          <option value="{{ grantId }}" {% if pageForm.grant|default(page.grant) == grantId %}selected{% endif %}>{{ grantLabel }}</option>
+          <option value="{{ grantId }}" {% if pageForm.grant|default(page.grant) == grantId %}selected{% endif %}>{{ t(grantLabel) }}</option>
           {% endfor %}
         </select>
         {% endif %}
         <input type="hidden" id="edit-form-csrf" name="_csrf" value="{{ csrf() }}">
-        <input type="submit" class="btn btn-primary" id="edit-form-submit" value="ページを更新" />
+        <input type="submit" class="btn btn-primary" id="edit-form-submit" value="{{ t('Update Page') }}" />
       </div>
     </div>
   </form>

+ 3 - 3
lib/views/layout/2column.html

@@ -19,7 +19,7 @@
   <div id="footer-container" class="footer">
     <footer class="">
       <p>
-      <a href="" data-target="#help-modal" data-toggle="modal"><i class="fa fa-question-circle"> ヘルプ</i></a>
+      <a href="" data-target="#help-modal" data-toggle="modal"><i class="fa fa-question-circle"></i> {{ t('Help') }}</a>
       &copy; {{ now|date('Y') }} {{ config.crowi['app:title']|default('Crowi') }} <img src="/logo/100x11_g.png" alt="powered by Crowi"> </p>
     </footer>
   </div>
@@ -31,12 +31,12 @@
 <div id="main" class="main col-md-9 {% if page %}{{ css.grant(page) }}{% endif %} {% block main_css_class %}{% endblock %}">
   {% if page && page.grant != 1 %}
   <p class="page-grant">
-    <i class="fa fa-lock"></i> {{ consts.pageGrants[page.grant] }} (このページの閲覧は制限されています)
+    <i class="fa fa-lock"></i> {{ consts.pageGrants[page.grant] }} ({{ t('Browsing of this page is restricted') }})
   </p>
   {% endif %}
   {% if page && page.grant == 2 %}
   <p class="alert alert-info">
-    このページの共有用URL
+    {{ t('Shareable Link') }}
     <input type="text" class="copy-link form-control" value="{{ baseUrl }}/{{ page._id.toString() }}" readonly>
   </p>
   {% endif %}

+ 5 - 5
lib/views/layout/layout.html

@@ -54,7 +54,7 @@
       {% if user and user.admin %}
       <li id="">
         <a href="/admin" id="link-mypage">
-          <i class="fa fa-cube"></i> 管理
+          <i class="fa fa-cube"></i> {{ t('Admin') }}
         </a>
       </li>
       {% endif %}
@@ -75,7 +75,7 @@
       #}
       <li id="" class="dropdown">
         <button class="btn btn-default create-page-button" data-target="#create-page" data-toggle="modal">
-          <i class="fa fa-pencil"></i> 作成
+          <i class="fa fa-pencil"></i> {{ t('New') }}
         </button>
       </li>
       <li id="login-user">
@@ -86,11 +86,11 @@
       <li class="dropdown">
         <a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-bars"></i> <label class="sr-only">メニュー</label></a>
         <ul class="dropdown-menu">
-          <li><a href="/me"><i class="fa fa-gears"></i> ユーザー設定</a></li>
+          <li><a href="/me"><i class="fa fa-gears"></i> {{ t('User Settings') }}</a></li>
           <li class="divider"></li>
-          <li><a href="/trash/"><i class="fa fa-trash-o"></i> 削除済みページ</a></li>
+          <li><a href="/trash/"><i class="fa fa-trash-o"></i> {{ t('Deleted Pages') }}</a></li>
           <li class="divider"></li>
-          <li><a href="/logout"><i class="fa fa-sign-out"></i> ログアウト</a></li>
+          <li><a href="/logout"><i class="fa fa-sign-out"></i> {{ t('Sign out') }}</a></li>
           {# <li><a href="#">今日の日報を作成</a></li> #}
           {# <li class="divider"></li> #}
           {# <li class="divider"></li> #}

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

@@ -20,7 +20,7 @@
 <div id="footer-container" class="footer">
   <footer class="">
     <p>
-    <a href="" data-target="#help-modal" data-toggle="modal"><i class="fa fa-question-circle"> ヘルプ</i></a>
+    <a href="" data-target="#help-modal" data-toggle="modal"><i class="fa fa-question-circle"></i> {{ t('Help') }}</a>
     &copy; {{ now|date('Y') }} {{ config.crowi['app:title'] }} <img src="/logo/100x11_g.png" alt="powered by Crowi"> </p>
   </footer>
 </div>

+ 25 - 25
lib/views/login.html

@@ -1,6 +1,6 @@
 {% extends 'layout/single-nologin.html' %}
 
-{% block html_title %}Login · {% endblock %}
+{% block html_title %}{{ t('Sign in') }} · {% endblock %}
 
 {% block content_main %}
 
@@ -18,7 +18,7 @@
 <div class="login-dialog flipper {% if req.query.register or req.body.registerForm or isRegistering or googleId %}to-flip{% endif %}" id="login-dialog">
 
   <div class="login-dialog-inner front">
-    <h2>ログイン</h2>
+    <h2>{{ t('Sign in') }}</h2>
 
     <div id="login-form-errors">
       {% set message = req.flash('warningMessage') %}
@@ -50,17 +50,17 @@
       </div>
 
       <input type="hidden" name="_csrf" value="{{ csrf() }}">
-      <input type="submit" class="btn btn-primary btn-lg btn-block" value="Login">
+      <input type="submit" class="btn btn-primary btn-lg btn-block" value="{{ t('Sign in') }}">
     </form>
 
     <hr>
 
     <div class="row">
       {% if googleLoginEnabled() %}
-      <div class="col-md-6">
-        <p>Google でログイン</p>
+      <div class="col-md-8">
+        <p>{{ t('Sign in by Google Account') }}</p>
         <form role="form" action="/login/google" method="get">
-          <button type="submit" class="btn btn-block btn-google"><i class="fa fa-google-plus-square"></i> Login</button>
+          <button type="submit" class="btn btn-block btn-google"><i class="fa fa-google-plus-square"></i> {{ t('Sign in') }}</button>
           <input type="hidden" name="_csrf" value="{{ csrf() }}">
         </form>
       </div>
@@ -68,19 +68,19 @@
     </div>
 
     {% if config.crowi['security:registrationMode'] != 'Closed' %}
-    <p class="bottom-text"><a href="#register" id="register"><i class="fa fa-pencil"></i> 新規登録はこちら</a></p>
+    <p class="bottom-text"><a href="#register" id="register"><i class="fa fa-pencil"></i> {{ t('Sign up is here') }}</a></p>
     {% endif %}
   </div>
 
   {% if config.crowi['security:registrationMode'] != 'Closed' %}
   <div class="register-dialog-inner back">
 
-    <h2>新規登録</h2>
+    <h2>{{ t('Sign up') }}</h2>
 
     {% if config.crowi['security:registrationMode'] == 'Restricted' %}
     <p class="alert alert-warning">
-    この Wiki への新規登録は制限されています。<br>
-    利用を開始するには、新規登録後、管理者による承認が必要です。
+      {{ t('page_register.notice.restricted') }}<br>
+      {{ t('page_register.notice.restricted_defail') }}
     </p>
     {% endif %}
 
@@ -91,8 +91,8 @@
         <img src="{{ googleImage }}" class="picture picture-rounded picture-lg">
       </p>
       {% endif %}
-      <code>{{ googleEmail }}</code> この Google アカウントで登録します<br>
-      ユーザーID、名前、パスワードを決めて登録を継続してください。
+      <code>{{ googleEmail }}</code> {{ t('page_register with this Google Account') }}<br>
+      {{ t('page_register.notice.google_account_continue') }}
     </div>
     {% endif %}
 
@@ -120,30 +120,30 @@
     <form role="form" method="post" action="/register" id="register-form">
       <input type="hidden" class="form-control" name="registerForm[googleId]" value="{{ googleId|default(req.body.registerForm.googleId) }}">
 
-      <label>ユーザーID</label>
+      <label>{{ t('User ID') }}</label>
       <div class="input-group" id="input-group-username">
         <span class="input-group-addon"><strong>@</strong></span>
-        <input type="text" class="form-control" placeholder="記入例: taroyama" name="registerForm[username]" value="{{ req.body.registerForm.username }}" required>
+        <input type="text" class="form-control" placeholder="{{ t('Example') }}: taroyama" name="registerForm[username]" value="{{ req.body.registerForm.username }}" required>
       </div>
       <p class="help-block">
       <span id="help-block-username" class="text-danger"></span>
-      ユーザーIDは、ユーザーページのURLなどに利用されます。半角英数字と一部の記号のみ利用できます。
+      {{ t('page_register.form_help.user_id') }}
       </p>
 
-      <label>名前</label>
+      <label>{{ t('Name') }}</label>
       <div class="input-group">
         <span class="input-group-addon"><i class="fa fa-user"></i></span>
-        <input type="text" class="form-control" placeholder="記入例: 山田 太郎" name="registerForm[name]" value="{{ googleName|default(req.body.registerForm.name) }}" required>
+        <input type="text" class="form-control" placeholder="{{ t('Example') }}: {{ t('Taro Yamada') }}" name="registerForm[name]" value="{{ googleName|default(req.body.registerForm.name) }}" required>
       </div>
 
-      <label >メールアドレス</label>
+      <label>{{ t('Email') }}</label>
       <div class="input-group">
         <span class="input-group-addon"><i class="fa fa-envelope"></i></span>
         <input type="email" class="form-control" placeholder="E-mail" name="registerForm[email]" value="{{ googleEmail|default(req.body.registerForm.email) }}" required>
       </div>
       {% if config.crowi['security:registrationWhiteList'] && config.crowi['security:registrationWhiteList'].length %}
       <p class="help-block">
-      この Wiki では以下のメールアドレスのみ登録可能です。
+        {{ t('page_register.form_help.email') }}
       </p>
       <ul>
         {% for em in config.crowi['security:registrationWhiteList'] %}
@@ -152,20 +152,20 @@
       </ul>
       {% endif %}
 
-      <label>パスワード</label>
+      <label>{{ t('Password') }}</label>
       <div class="input-group">
         <span class="input-group-addon"><i class="fa fa-key"></i></span>
         <input type="password" class="form-control" placeholder="Password" name="registerForm[password]" required>
       </div>
       <p class="help-block">
-      パスワードは6文字以上の半角英数字または記号
+        {{ t('page_register.form_help.password') }}
       </p>
 
       {% if googleImage %}
         <input type="hidden" name="registerForm[googleImage]" value="{{ googleImage }}">
       {% endif  %}
       <input type="hidden" name="_csrf" value="{{ csrf() }}">
-      <input type="submit" class="btn btn-primary btn-lg btn-block" value="新規登録">
+      <input type="submit" class="btn btn-primary btn-lg btn-block" value="{{ t('Sign up') }}">
     </form>
 
     <hr>
@@ -173,16 +173,16 @@
     <div class="row">
       {% if googleLoginEnabled() %}
       <div class="col-md-6">
-        <p>Google で登録</p>
+        <p>{{ t('Sign up with Google Account') }}</p>
         <form role="form" method="post" action="/register/google">
           <input type="hidden" name="_csrf" value="{{ csrf() }}">
-          <button type="submit" class="btn btn-block btn-google"><i class="fa fa-google-plus-square"></i> Login</button>
+          <button type="submit" class="btn btn-block btn-google"><i class="fa fa-google-plus-square"></i> {{ t('Login') }}</button>
         </form>
       </div>
       {% endif %}
     </div>
 
-    <p class="bottom-text"><a href="#login" id="login"><i class="fa fa-sign-out"></i> ログインはこちら</a></p>
+    <p class="bottom-text"><a href="#login" id="login"><i class="fa fa-sign-out"></i> {{ t('Sign in is here') }}</a></p>
   </div>
   {% endif %} {# if registrationMode == Closed #}
 

+ 12 - 12
lib/views/me/api_token.html

@@ -1,12 +1,12 @@
 {% extends '../layout/2column.html' %}
 
 
-{% block html_title %}APIの設定 · {{ path }}{% endblock %}
+{% block html_title %}{{ t('API Settings') }} · {{ path }}{% endblock %}
 
 {% block content_head %}
 <div class="header-wrap">
   <header id="page-header">
-  <h1 class="title" id="">ユーザー設定</h1>
+  <h1 class="title" id="">{{ t('User Settings') }}</h1>
   </header>
 </div>
 {% endblock %}
@@ -15,9 +15,9 @@
 <div class="content-main">
 
   <ul class="nav nav-tabs">
-    <li><a href="/me"><i class="fa fa-gears"></i> ユーザー情報</a></li>
-    <li><a href="/me/password"><i class="fa fa-key"></i> パスワード設定</a></li>
-    <li class="active"><a href="/me/apiToken"><i class="fa fa-rocket"></i> API設定</a></li>
+    <li><a href="/me"><i class="fa fa-gears"></i> {{ t('User Information') }}</a></li>
+    <li><a href="/me/password"><i class="fa fa-key"></i> {{ t('Password Setting') }}</a></li>
+    <li class="active"><a href="/me/apiToken"><i class="fa fa-rocket"></i> {{ t('API Settings') }}</a></li>
   </ul>
 
   <div class="tab-content">
@@ -43,29 +43,29 @@
 
     <form action="/me/apiToken" method="post" class="form-horizontal" role="form">
     <fieldset>
-      <legend>API Token 設定</legend>
+      <legend>{{ t('API Token Settings') }}</legend>
       <div class="form-group {% if not user.password %}has-error{% endif %}">
-        <label for="" class="col-xs-2 control-label">現在のAPI Token</label>
+        <label for="" class="col-xs-3 control-label">{{ t('Current API Token') }}</label>
         <div class="col-xs-6">
           {% if user.apiToken %}
             <input class="form-control" type="text" value="{{ user.apiToken }}">
           {% else %}
           <p class="form-control-static">
-            API Token が設定されていません。更新するボタンから発行してください。
+            {{ t('page_me_apitoken.notice.apitoken_issued') }}
           </p>
           {% endif %}
         </div>
       </div>
 
       <div class="form-group">
-        <div class="col-xs-offset-2 col-xs-10">
+        <div class="col-xs-offset-3 col-xs-10">
 
           <p class="alert alert-warning">
-          API Token を更新すると、自動的に新しい Token が生成されます。<br>
-          現在の Token を利用している処理は動かなくなります。
+            {{ t('page_me_apitoken.notice.update_token1') }}<br>
+            {{ t('page_me_apitoken.notice.update_token2') }}
           </p>
 
-          <button type="submit" value="1" name="apiTokenForm[confirm]" class="btn btn-primary">API Tokenを更新する</button>
+          <button type="submit" value="1" name="apiTokenForm[confirm]" class="btn btn-primary">{{ t('Update API Token') }}</button>
         </div>
       </div>
 

+ 34 - 34
lib/views/me/index.html

@@ -1,11 +1,11 @@
 {% extends '../layout/2column.html' %}
 
-{% block html_title %}ユーザー設定 · {{ path }}{% endblock %}
+{% block html_title %}{{ t('User Settings') }} · {{ path }}{% endblock %}
 
 {% block content_head %}
 <div class="header-wrap">
   <header id="page-header">
-    <h1 class="title" id="">ユーザー設定</h1>
+    <h1 class="title" id="">{{ t('User Settings') }}</h1>
   </header>
 </div>
 {% endblock %}
@@ -14,9 +14,9 @@
 <div class="content-main">
 
   <ul class="nav nav-tabs">
-    <li class="active"><a href="/me"><i class="fa fa-gears"></i> ユーザー情報</a></li>
-    <li><a href="/me/password"><i class="fa fa-key"></i> パスワード設定</a></li>
-    <li><a href="/me/apiToken"><i class="fa fa-rocket"></i> API設定</a></li>
+    <li class="active"><a href="/me"><i class="fa fa-gears"></i> {{ t('User Information') }}</a></li>
+    <li><a href="/me/password"><i class="fa fa-key"></i> {{ t('Password Settings') }}</a></li>
+    <li><a href="/me/apiToken"><i class="fa fa-rocket"></i> {{ t('API Settings') }}</a></li>
   </ul>
 
   <div class="tab-content">
@@ -49,31 +49,22 @@
   <div class="form-box">
     <form action="/me" method="post" class="form-horizontal" role="form">
       <fieldset>
-        <legend>ユーザーの基本情報</legend>
+        <legend>{{ t('Basic Info') }}</legend>
       <div class="form-group">
-        <label for="userForm[name]" class="col-sm-2 control-label">名前</label>
+        <label for="userForm[name]" class="col-sm-2 control-label">{{ t('Name') }}</label>
         <div class="col-sm-4">
           <input class="form-control" type="text" name="userForm[name]" value="{{ user.name }}" required>
         </div>
       </div>
       <div class="form-group {% if not user.email %}has-error{% endif %}">
-        <label for="userForm[email]" class="col-sm-2 control-label">メールアドレス</label>
+        <label for="userForm[email]" class="col-sm-2 control-label">{{ t('Email') }}</label>
         <div class="col-sm-4">
           <input class="form-control" type="email" name="userForm[email]" value="{{ user.email }}" required>
         </div>
         <div class="col-sm-offset-2 col-sm-10">
-          {# ↓ そのうちこのコードは削除する #}
-          {% if not user.email %}
-          <p class="help-block help-danger">
-          メールアドレスは登録必須項目です。<br>
-          (以前のバージョンのWikiで作成されたユーザー情報の場合、メールアドレスが登録されていません)<br>
-          更新ボタンを押して新規登録してください。
-          </p>
-          {% endif %}
-
           {% if config.crowi['security:registrationWhiteList'] && config.crowi['security:registrationWhiteList'].length %}
           <p class="help-block">
-          この Wiki では以下のメールアドレスのみ登録可能です。
+            {{ t('page_register.form_help.email') }}
           <ul>
             {% for em in config.crowi['security:registrationWhiteList'] %}
             <li><code>{{ em }}</code></li>
@@ -82,11 +73,20 @@
           </p>
           {% endif %}
         </div>
+        <div class="form-group {% if not user.lang %}has-error{% endif %}">
+          <label for="userForm[lang]" class="col-sm-2 control-label">{{ t('Language') }}</label>
+          <div class="col-sm-4 radio">
+            <label><input type="radio" name="userForm[lang]" value="{{ consts.language.LANG_EN_US }}" {% if user.lang == consts.language.LANG_EN_US %}checked="checked"{% endif %}>{{ t('English') }}</label>
+            <label><input type="radio" name="userForm[lang]" value="{{ consts.language.LANG_JA }}" {% if user.lang == consts.language.LANG_JA %}checked="checked"{% endif %}>{{ t('Japanese') }}</label>
+          </div>
+          <div class="col-sm-offset-2 col-sm-10">
+          </div>
+        </div>
       </div>
 
       <div class="form-group">
         <div class="col-sm-offset-2 col-sm-10">
-          <button type="submit" class="btn btn-primary">更新</button>
+          <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
         </div>
       </div>
     </fieldset>
@@ -95,11 +95,11 @@
 
   <div class="form-box">
     <fieldset>
-      <legend>プロフィール画像の設定</legend>
+      <legend>{{ t('Set Profile Image') }}</legend>
         <div class="form-group">
           <div id="pictureUploadFormMessage"></div>
           <label for="" class="col-sm-3 control-label">
-            現在の画像
+            {{ t('Current Image') }}
           </label>
           <div class="col-sm-9">
             <p>
@@ -107,8 +107,8 @@
             </p>
             <p>
             {% if user.image %}
-            <form action="/me/picture/delete" method="post" class="form-horizontal" role="form" onsubmit="return window.confirm('削除してよろしいですか?');">
-              <button type="submit" class="btn btn-danger">画像を削除</button>
+            <form action="/me/picture/delete" method="post" class="form-horizontal" role="form" onsubmit="return window.confirm('{{ t('Delete this image?') }}');">
+              <button type="submit" class="btn btn-danger">{{ t('Delete Image') }}</button>
             </form>
             {% endif %}
             </p>
@@ -117,7 +117,7 @@
 
         <div class="form-group">
           <label for="" class="col-sm-3 control-label">
-            新しい画像をアップロード
+            {{ t('Upload new image') }}
           </label>
           <div class="col-sm-9">
             {% if isUploadable() %}
@@ -127,8 +127,8 @@
               </div>
             </form>
             {% else %}
-            * 画像をアップロードをするための設定がされていません。<br>
-            * アップロードできるようにするには、AWS またはローカルアップロードの設定をしてください。<br>
+            * {{ t('page_me.form_help.profile_image1') }}<br>
+            * {{ t('page_me.form_help.profile_image2') }}<br>
             {% endif %}
           </div>
         </div>
@@ -178,7 +178,7 @@
       <div class="form-box">
         <form action="/me/auth/google" method="post" class="form-horizontal" role="form">
           <fieldset>
-            <legend><i class="fa fa-google-plus-square"></i> Google設定</legend>
+            <legend><i class="fa fa-google-plus-square"></i> {{ t('Google Setting') }}</legend>
 
             {% set wmessage = req.flash('warningMessage.auth.google') %}
             {% if wmessage.length %}
@@ -192,13 +192,13 @@
 
             <div class="col-sm-12">
               <p>
-                接続されています
+                {{ t('Connected') }}
 
-                <input type="submit" name="disconnectGoogle" class="btn btn-default" value="接続を解除">
+                <input type="submit" name="disconnectGoogle" class="btn btn-default" value="{{ t('Disconnect') }}">
               </p>
               <p class="help-block">
-              接続を解除すると、Googleでログインができなくなります。<br>
-              解除後はメールアドレスとパスワードでログインすることができます。
+                {{ t('page_me.form_help.google_disconnect1') }}<br>
+                {{ t('page_me.form_help.google_disconnect2') }}
               </p>
             </div>
 
@@ -209,11 +209,12 @@
                 <input type="submit" name="connectGoogle" class="btn btn-google" value="Googleコネクト">
               </div>
               <p class="help-block">
-              Googleコネクトをすると、Googleアカウントでログイン可能になります。<br>
+                {{ t('page_me.form_help.google_connect1') }}<br>
               </p>
               {% if config.crowi['security:registrationWhiteList'] && config.crowi['security:registrationWhiteList'].length %}
               <p class="help-block">
-              この Wiki では、登録可能なメールアドレスが限定されているため、コネクト可能なGoogleアカウントは、以下のメールアドレスの発行できるGoogle Appsアカウントに限られます。
+                {{ t('page_register.form_help.email') }}<br>
+                {{ t('page_me.form_help.google_connect2') }}
               </p>
               <ul>
                 {% for em in config.crowi['security:registrationWhiteList'] %}
@@ -271,4 +272,3 @@
 
 {% block footer %}
 {% endblock footer %}
-

+ 14 - 14
lib/views/me/password.html

@@ -1,11 +1,11 @@
 {% extends '../layout/2column.html' %}
 
-{% block html_title %}パスワードの設定 · {{ path }}{% endblock %}
+{% block html_title %}{{ t('Password Settings') }} · {{ path }}{% endblock %}
 
 {% block content_head %}
 <div class="header-wrap">
   <header id="page-header">
-    <h1 class="title" id="">ユーザー設定</h1>
+    <h1 class="title" id="">{{ t('User Settings') }}</h1>
   </header>
 </div>
 {% endblock %}
@@ -14,16 +14,16 @@
 <div class="content-main">
 
   <ul class="nav nav-tabs">
-    <li><a href="/me"><i class="fa fa-gears"></i> ユーザー情報</a></li>
-    <li class="active"><a href="/me/password"><i class="fa fa-key"></i> パスワード設定</a></li>
-    <li><a href="/me/apiToken"><i class="fa fa-rocket"></i> API設定</a></li>
+    <li><a href="/me"><i class="fa fa-gears"></i> {{ t('User Information') }}</a></li>
+    <li class="active"><a href="/me/password"><i class="fa fa-key"></i> {{ t('Password Settings') }}</a></li>
+    <li><a href="/me/apiToken"><i class="fa fa-rocket"></i> {{ t('API Settings') }}</a></li>
   </ul>
 
   <div class="tab-content">
 
   {% if not user.password %}
   <div class="alert alert-danger">
-    パスワードを設定してください
+    {{ t('Please set a password') }}
   </div>
   {% endif %}
 
@@ -46,7 +46,7 @@
 
   {% if user.email %}
   <p>
-  <code>{{ user.email }}</code> と設定されたパスワードの組み合わせでログイン可能になります。
+    {{ t('You can sign in with email and password', user.email) }}
   </p>
   {% endif %}
 
@@ -55,37 +55,37 @@
     <form action="/me/password" method="post" class="form-horizontal" role="form">
     <fieldset>
       {% if user.password %}
-      <legend>パスワードを更新</legend>
+      <legend>{{ t('Update Password') }}</legend>
       {% else %}
-      <legend>パスワードを新規に設定</legend>
+      <legend>{{ t('Set new Password') }}</legend>
       {% endif %}
       {% if user.password %}
       <div class="form-group">
-        <label for="mePassword[oldPassword]" class="col-xs-2 control-label">現在のパスワード</label>
+        <label for="mePassword[oldPassword]" class="col-xs-3 control-label">{{ t('Current password') }}</label>
         <div class="col-xs-6">
           <input class="form-control" type="password" name="mePassword[oldPassword]">
         </div>
       </div>
       {% endif %}
       <div class="form-group {% if not user.password %}has-error{% endif %}">
-        <label for="mePassword[newPassword]" class="col-xs-2 control-label">新しいパスワード</label>
+        <label for="mePassword[newPassword]" class="col-xs-3 control-label">{{ t('New password') }}</label>
         <div class="col-xs-6">
           <input class="form-control" type="password" name="mePassword[newPassword]" required>
         </div>
       </div>
       <div class="form-group">
-        <label for="mePassword[newPasswordConfirm]" class="col-xs-2 control-label">確認</label>
+        <label for="mePassword[newPasswordConfirm]" class="col-xs-3 control-label">{{ t('Re-enter new password') }}</label>
         <div class="col-xs-6">
           <input class="form-control col-xs-4" type="password" name="mePassword[newPasswordConfirm]" required>
 
-          <p class="help-block">パスワードには、6文字以上の半角英数字または記号等を設定してください。</p>
+          <p class="help-block">{{ t('page_register.form_help.password') }}</p>
         </div>
       </div>
 
 
       <div class="form-group">
         <div class="col-xs-offset-2 col-xs-10">
-          <button type="submit" class="btn btn-primary">更新</button>
+          <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
         </div>
       </div>
 

+ 8 - 9
lib/views/modal/create_page.html

@@ -4,7 +4,7 @@
 
       <div class="modal-header">
         <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-        <h4 class="modal-title">New Page</h4>
+        <h4 class="modal-title">{{ t('New Page') }}</h4>
       </div>
 
       <div class="modal-body">
@@ -12,16 +12,16 @@
         <form class="form-horizontal" id="create-page-today" role="form">
           <fieldset>
             <div class="col-xs-12">
-              <h4>今日の◯◯を作成</h4>
+              <h4>{{ t("Create today's") }}</h4>
             </div>
             <div class="col-xs-10">
               <span class="page-today-prefix">{{ userPageRoot(user) }}/</span>
-              <input type="text" data-prefix="{{ userPageRoot(user) }}/" class="page-today-input1 form-control" value="メモ" id="" name="">
+              <input type="text" data-prefix="{{ userPageRoot(user) }}/" class="page-today-input1 form-control" value="{{ t('Memo') }}" id="" name="">
               <span class="page-today-suffix">/{{ now|datetz('Y/m/d') }}/</span>
-              <input type="text" data-prefix="/{{ now|datetz('Y/m/d') }}/" class="page-today-input2 form-control" id="page-today-input2" name="" placeholder="ページ名を入力(空欄OK)">
+              <input type="text" data-prefix="/{{ now|datetz('Y/m/d') }}/" class="page-today-input2 form-control" id="page-today-input2" name="" placeholder="{{ t('Input page name (optional)') }}">
             </div>
             <div class="col-xs-2">
-              <button type="submit" class="btn btn-primary">作成</button>
+              <button type="submit" class="btn btn-primary">{{ t('Create') }}</button>
             </div>
           </fieldset>
         </form>
@@ -31,13 +31,13 @@
         <form class="form-horizontal" id="create-page-under-tree" role="form">
           <fieldset>
             <div class="col-xs-12">
-              <h4><code>{{ parentPath(path) }}</code>以下に作成</h4>
+              <h4>{{ t('Create under', parentPath(path)) }}</h4>
             </div>
             <div class="col-xs-10">
-              <input type="text" value="{{ parentPath(path) }}" class="page-name-input form-control " placeholder="ページ名を入力" required>
+              <input type="text" value="{{ parentPath(path) }}" class="page-name-input form-control " placeholder="{{ t('Input page name') }}" required>
             </div>
             <div class="col-xs-2">
-              <button type="submit" class="btn btn-primary">作成</button>
+              <button type="submit" class="btn btn-primary">{{ t('Create') }}</button>
             </div>
           </fieldset>
         </form>
@@ -49,4 +49,3 @@
     </div><!-- /.modal-content -->
   </div><!-- /.modal-dialog -->
 </div><!-- /.modal -->
-

+ 18 - 18
lib/views/modal/help.html

@@ -4,50 +4,50 @@
 
       <div class="modal-header">
         <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-        <h4 class="modal-title">ヘルプ</h4>
+        <h4 class="modal-title">{{ t('Help') }}</h4>
       </div>
       <div class="modal-body">
-        <h4>基本的な機能</h4>
+        <h4>{{ t('modal_help.basic.title') }}</h4>
         <br>
         <ul>
-          <li>表示される画面には、「一覧ページ」と「ページ」の2種類があります</li>
-          <li>スラッシュ <code>/</code> で終わるページは、その階層の一覧ページとなります。</li>
-          <li>ページでの変更はすべて記録されています。サイドバーには、変更の履歴が一覧となっていて、クリックするとそのページの過去の状態を見ることができます。</li>
+          <li>{{ t('modal_help.basic.body1') }}</li>
+          <li>{{ t('modal_help.basic.body2') }}</li>
+          <li>{{ t('modal_help.basic.body3') }}</li>
         </ul>
         <br>
 
-        <h4>編集のコツ</h4>
+        <h4>{{ t('modal_help.tips.title') }}</h4>
         <br>
         <p>
-        文章の <strong>構造</strong> を意識しましょう。本を書くように、内容と文脈を整理してセクション・サブセクション...と構造的に書くと、わかりやすく他人に伝わりやすいページがになります。
+        {{ t('modal_help.tips.body1') }}
         </p>
         <br>
 
-        <h4>記法</h4>
+        <h4>{{ t('modal_help.markdown.title') }}</h4>
         <br>
         <div class="wiki">
-        <pre># セクション</pre>
-        <h1>セクション</h1>
+        <pre># Section</pre>
+        <h1>Section</h1>
         </div>
         <hr>
 
         <div class="wiki">
-        <pre>## サブセクション</pre>
-        <h2>サブセクション</h2>
+        <pre>## Sub section</pre>
+        <h2>Sub Section</h2>
         </div>
         <hr>
 
         <div class="wiki">
-        <pre>### サブサブセクション</pre>
-        <h3>サブサブセクション</h3>
+        <pre>### Sub sub section</pre>
+        <h3>Sub Sub Section</h3>
         </div>
         <hr>
 
         <div class="wiki">
-        <pre>* このようにアスタリスクと半角スペースを先頭に書くと、
-* 箇条書きのリストにになります
-    * タブキーを押すと半角スペース4つが挿入され、インデントされます
-    * インデントはリストにも反映されます</pre>
+        <pre>- このようにハイフンと半角スペースを先頭に書くと、
+- 箇条書きのリストにになります
+    - タブキーを押すと半角スペース4つが挿入され、インデントされます
+    - インデントはリストにも反映されます</pre>
           <ul>
             <li>リスト記法はこのように</li>
             <li>箇条書きになります

+ 6 - 9
lib/views/modal/rename.html

@@ -6,19 +6,15 @@
 
         <div class="modal-header">
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-          <h4 class="modal-title">ページを移動する</h4>
+          <h4 class="modal-title">{{ t('Rename page') }}</h4>
         </div>
         <div class="modal-body">
-          <ul>
-           <li>移動先にページが存在する場合は、移動できません。</li>
-           <li>過去の履歴も含めてすべて移動されます。</li>
-          </ul>
             <div class="form-group">
-              <label for="">このページ</label><br>
+              <label for="">{{ t('Current page name') }}</label><br>
               <code>{{ page.path }}</code>
             </div>
             <div class="form-group">
-              <label for="newPageName">移動先のページ名</label><br>
+              <label for="newPageName">{{ t('New page name') }}</label><br>
               <div class="input-group">
                 <span class="input-group-addon">{{ config.crowi['app:url'] }}</span>
                 <input type="text" class="form-control" name="new_path" id="newPageName" value="{{ page.path }}">
@@ -26,9 +22,10 @@
             </div>
             <div class="checkbox">
                <label>
-                 <input name="create_redirect" value="1"  type="checkbox"> リダイレクトページを作成
+                 <input name="create_redirect" value="1"  type="checkbox"> {{ t('Redirect') }}
                </label>
-               <p class="help-block">チェックを入れると、<code>{{ page.path }}</code>にアクセスされた際に自動的に新しいページにジャンプします。</p>
+               <p class="help-block"> {{ t('modal_rename.help.redirect', page.path) }}
+               </p>
             </div>
             {# <div class="checkbox"> #}
             {#    <label> #}

+ 17 - 17
lib/views/page.html

@@ -67,7 +67,7 @@
   <ul class="nav nav-tabs hidden-print">
     <li><a>Create: {{ path }}</a></li>
     <li class="dropdown pull-right">
-      <a href="#" onclick="history.back();"><i class="fa fa-times"></i> キャンセル</a>
+      <a href="#" onclick="history.back();"><i class="fa fa-times"></i> {{ t('Cancel') }}</a>
     </li>
   </ul>
   <div class="tab-content">
@@ -107,7 +107,7 @@
       </a>
     </li>
 
-    <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> Edit</a></li>
+    <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> {{ t('Edit') }}</a></li>
 
 
     <li class="dropdown pull-right">
@@ -115,11 +115,11 @@
         <i class="fa fa-wrench"></i> <span class="caret"></span>
       </a>
       <ul class="dropdown-menu">
-       <li><a href="#" data-target="#renamePage" data-toggle="modal"><i class="fa fa-share"></i> 移動</a></li>
-       <li><a href="?presentation=1" class="toggle-presentation"><i class="fa fa-arrows-alt"></i> プレゼンモード (beta)</a></li>
+       <li><a href="#" data-target="#renamePage" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Move') }}</a></li>
+       <li><a href="?presentation=1" class="toggle-presentation"><i class="fa fa-arrows-alt"></i> {{ t('Presentation Mode') }} (beta)</a></li>
        {% if isDeletablePage() %}
        <li class="divider"></li>
-       <li class=""><a href="#" data-target="#deletePage" data-toggle="modal"><i class="fa fa-trash-o text-danger"></i> 削除</a></li>
+       <li class=""><a href="#" data-target="#deletePage" data-toggle="modal"><i class="fa fa-trash-o text-danger"></i> {{ t('Delete') }}</a></li>
        {% endif %}
       </ul>
     </li>
@@ -132,12 +132,12 @@
   <div class="tab-content wiki-content">
   {% if req.query.renamed and not page.isDeleted() %}
   <div class="alert alert-info">
-    <strong>移動しました: </strong> このページは <code>{{ req.query.renamed }}</code> から移動しました。
+    <strong>{{ t('Moved') }}: </strong> {{ t('page_page.notice.moved', req.query.renamed) }}
   </div>
   {% endif %}
   {% if not page.isLatestRevision() %}
   <div class="alert alert-warning">
-    <strong>注意: </strong> これは現在の版ではありません。 <i class="fa fa-magic"></i> <a href="{{ page.path }}">最新のページを表示</a>
+    <strong>{{ t('Warning') }}: </strong> {{ t('page_page.notice.version') }} <i class="fa fa-magic"></i> <a href="{{ page.path }}">{{ t('Show latest') }}</a>
   </div>
   {% endif %}
 
@@ -154,7 +154,7 @@
     {# formatted text #}
     <div class="tab-pane {% if not req.body.pageForm %}active{% endif %}" id="revision-body">
       <div class="revision-toc" id="revision-toc">
-        <a data-toggle="collapse" data-parent="#revision-toc" href="#revision-toc-content" class="revision-toc-head collapsed">目次</a>
+        <a data-toggle="collapse" data-parent="#revision-toc" href="#revision-toc-content" class="revision-toc-head collapsed">{{ t('Table of Contents') }}</a>
 
       </div>
       <div class="wiki" id="revision-body-content"></div>
@@ -173,23 +173,23 @@
       {% if not page %}
       {% else %}
       <div class="revision-history-list">
-        {% for t in tree %}
+        {% for tt in tree %}
         <div class="revision-hisory-outer">
-          <img src="{{ t.author|picture }}" class="picture picture-rounded">
+          <img src="{{ tt.author|picture }}" class="picture picture-rounded">
           <div class="revision-history-main">
             <div class="revision-history-author">
-              <strong>{% if t.author %}{{ t.author.username }}{% else %}-{% endif %}</strong>
+              <strong>{% if tt.author %}{{ tt.author.username }}{% else %}-{% endif %}</strong>
             </div>
             <div class="revision-history-comment">
             </div>
             <div class="revision-history-meta">
-              {{ t.createdAt|datetz('Y-m-d H:i:s') }}
+              {{ tt.createdAt|datetz('Y-m-d H:i:s') }}
               <br>
-              <a href="?revision={{ t._id.toString() }}"><i class="fa fa-history"></i> このバージョンを見る</a>
-              <a class="diff-view" data-revision-id="{{ t._id.toString() }}">
-                <i id="diff-icon-{{ t._id.toString() }}" class="fa fa-arrow-circle-right"></i> 差分を見る
+              <a href="?revision={{ tt._id.toString() }}"><i class="fa fa-history"></i> {{ t('View this version') }}</a>
+              <a class="diff-view" data-revision-id="{{ tt._id.toString() }}">
+                <i id="diff-icon-{{ tt._id.toString() }}" class="fa fa-arrow-circle-right"></i> {{ t('View diff') }}
               </a>
-              <pre class="" id="diff-display-{{ t._id.toString()}}" style="display: none"></pre>
+              <pre class="" id="diff-display-{{ tt._id.toString()}}" style="display: none"></pre>
             </div>
           </div>
         </div>
@@ -202,7 +202,7 @@
   </div>
   {% endif %}
 
-<div id="notifPageEdited" class="fk-notif fk-notif-danger"><i class="fa fa-exclamation-triangle"></i> <span class="edited-user"></span>さんがこのページを編集しました。 <a href="javascript:location.reload();"><i class="fa fa-angle-double-right"></i> 最新版を読み込む</a></div>
+  <div id="notifPageEdited" class="fk-notif fk-notif-danger"><i class="fa fa-exclamation-triangle"></i> <span class="edited-user"></span> {{ t('edited this page') }} <a href="javascript:location.reload();"><i class="fa fa-angle-double-right"></i> {{ t('Load latest') }}</a></div>
 </div>
 
 {% block content_main_after %}

+ 13 - 13
lib/views/page_list.html

@@ -88,7 +88,7 @@
       <a>Create Portal: {{ path }}</a>
       {% endif %}
     </li>
-    <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> 編集</a></li>
+    <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> {{ t('Edit') }}</a></li>
 
     {% if not page %}
     <li class="pull-right close-button">
@@ -102,7 +102,7 @@
         <i class="fa fa-wrench"></i> <span class="caret"></span>
       </a>
       <ul class="dropdown-menu">
-       <li><a href="#" data-target="#unportalize" data-toggle="modal"><i class="fa fa-share"></i> ポータル解除</a></li>
+        <li><a href="#" data-target="#unportalize" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Unportalize') }}</a></li>
       </ul>
     </li>
     <li class="pull-right"><a href="#revision-history" data-toggle="tab"><i class="fa fa-history"></i> History</a></li>
@@ -112,7 +112,7 @@
   <div class="tab-content">
   {% if page and not page.isLatestRevision() %}
   <div class="alert alert-warning">
-    <strong>注意: </strong> これは現在の版ではありません。 <i class="fa fa-magic"></i> <a href="{{ page.path }}">最新のポータルを表示</a>
+    <strong>{{ t('Warning') }}: </strong> {{ t('page.notice.version') }} <i class="fa fa-magic"></i> <a href="{{ page.path }}">最新のポータルを表示</a>
   </div>
   {% endif %}
     <div class="tab-pane {% if not req.body.pageForm %}active{% endif %}" id="revision-body">
@@ -132,23 +132,23 @@
       {% if not page %}
       {% else %}
       <div class="revision-history-list">
-        {% for t in tree %}
+        {% for tr in tree %}
         <div class="revision-hisory-outer">
-          <img src="{{ t.author|picture }}" class="picture picture-rounded">
+          <img src="{{ tr.author|picture }}" class="picture picture-rounded">
           <div class="revision-history-main">
             <div class="revision-history-author">
-              <strong>{% if t.author %}{{ t.author.username }}{% else %}-{% endif %}</strong>
+              <strong>{% if tr.author %}{{ tr.author.username }}{% else %}-{% endif %}</strong>
             </div>
             <div class="revision-history-comment">
             </div>
             <div class="revision-history-meta">
-              {{ t.createdAt|datetz('Y-m-d H:i:s') }}
+              {{ tr.createdAt|datetz('Y-m-d H:i:s') }}
               <br>
-              <a href="?revision={{ t._id.toString() }}"><i class="fa fa-history"></i> このバージョンを見る</a>
-              <a class="diff-view" data-revision-id="{{ t._id.toString() }}">
-                <i id="diff-icon-{{ t._id.toString() }}" class="fa fa-arrow-circle-right"></i> 差分を見る
+              <a href="?revision={{ tr._id.toString() }}"><i class="fa fa-history"></i> {{ t('View this version') }}</a>
+              <a class="diff-view" data-revision-id="{{ tr._id.toString() }}">
+                <i id="diff-icon-{{ tr._id.toString() }}" class="fa fa-arrow-circle-right"></i> {{ t('View diff') }}
               </a>
-              <pre class="" id="diff-display-{{ t._id.toString()}}" style="display: none"></pre>
+              <pre class="" id="diff-display-{{ tr._id.toString()}}" style="display: none"></pre>
             </div>
           </div>
         </div>
@@ -162,8 +162,8 @@
 
 <div class="page-list-container">
   <ul class="nav nav-tabs">
-      <li class="active"><a href="#view-list" data-toggle="tab">リスト表示</a></li>
-      <li><a href="#view-timeline" data-toggle="tab">タイムライン表示</a></li>
+      <li class="active"><a href="#view-list" data-toggle="tab">{{ t('List View') }}</a></li>
+      <li><a href="#view-timeline" data-toggle="tab">{{ t('Timeline View') }}</a></li>
   </ul>
 
   <div class="tab-content">

+ 3 - 1
lib/views/user_page.html

@@ -36,9 +36,11 @@
       <li>
         <a href="#user-created-list" data-toggle="tab"><i class="fa fa-pencil"></i> Recent Created</a>
       </li>
+      {% if user._id.toString() == pageUser._id.toString() %}
       <li>
-        <a href="/me"><i class="fa fa-gears"></i> Setting</a>
+        <a href="/me"><i class="fa fa-gears"></i> Settings</a>
       </li>
+      {% endif %}
     </ul>
     <div class="user-page-content-tab tab-content">
 

+ 4 - 4
lib/views/widget/page_side_content.html

@@ -1,10 +1,10 @@
-<h3><i class="fa fa-link"></i> Share</h3>
+<h3><i class="fa fa-link"></i> {{ t('Share') }}</h3>
 <ul class="fitted-list">
-  <li data-toggle="tooltip" data-placement="bottom" title="共有用リンク" class="input-group">
-    <span class="input-group-addon">共有用</span>
+  <li class="input-group">
+    <span class="input-group-addon">{{ t('Share Link') }}</span>
     <input readonly class="copy-link form-control" type="text" value="{{ config.crowi['app:title']|default('Crowi') }} {{ path }}  {{ baseUrl }}/{{ page._id.toString() }}">
   </li>
-  <li data-toggle="tooltip" data-placement="bottom" title="Markdown形式のリンク" class="input-group">
+  <li class="input-group">
     <span class="input-group-addon">Markdown</span>
     <input readonly class="copy-link form-control" type="text" value="[{{ path }}]({{ baseUrl }}/{{ page._id.toString() }})">
   </li>

+ 6 - 6
lib/views/widget/page_side_header.html

@@ -12,13 +12,13 @@
         <a href="{{ userPageRoot(page.creator) }}">{{ page.creator.name|default(author.name) }}</a>
       </p>
       <p class="created-at">
-        作成日: {{ page.createdAt|datetz('Y/m/d H:i:s') }}<br>
+        {{ t('Created') }}: {{ page.createdAt|datetz('Y/m/d H:i:s') }}<br>
 
         {% if page.lastUpdateUser %}
-          最終更新: {{ page.updatedAt|datetz('Y/m/d H:i:s') }} <a href="/user/{{ page.lastUpdateUser.username }}"><img src="{{ page.lastUpdateUser|picture }}" class="picture picture-xs picture-rounded" alt="{{ page.lastUpdateUser.name }}"></a>
+          {{ t('Last updated') }}: {{ page.updatedAt|datetz('Y/m/d H:i:s') }} <a href="/user/{{ page.lastUpdateUser.username }}"><img src="{{ page.lastUpdateUser|picture }}" class="picture picture-xs picture-rounded" alt="{{ page.lastUpdateUser.name }}"></a>
         {% else %}
           {# for BC 1.5.x #}
-          最終更新: {{ page.updatedAt|datetz('Y/m/d H:i:s') }} <a href="/user/{{ page.revision.author.username }}"><img src="{{ page.revision.author|picture }}" class="picture picture-xs picture-rounded" alt="{{ page.revision.author.name }}"></a>
+          {{ t('Last updated') }}: {{ page.updatedAt|datetz('Y/m/d H:i:s') }} <a href="/user/{{ page.revision.author.username }}"><img src="{{ page.revision.author|picture }}" class="picture picture-xs picture-rounded" alt="{{ page.revision.author.name }}"></a>
         {% endif %}
       </p>
     </div>
@@ -27,7 +27,7 @@
   <div class="like-box">
     <dl class="dl-horizontal">
       <dt>
-        <i class="fa fa-thumbs-o-up"></i> いいね!
+        <i class="fa fa-thumbs-o-up"></i> {{ t('Like!') }}
       </dt>
       <dd>
         <p class="liker-count">
@@ -36,13 +36,13 @@
           data-csrftoken="{{ csrf() }}"
           data-liked="{% if page.isLiked(user) %}1{% else %}0{% endif %}"
           class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
-          ><i class="fa fa-thumbs-o-up"></i> いいね!</button>
+          ><i class="fa fa-thumbs-o-up"></i> {{ t('Like!') }}</button>
         </p>
         <p id="liker-list" class="liker-list" data-likers="{{ page.liker|default([])|join(',') }}">
         </p>
       </dd>
 
-      <dt><i class="fa fa-paw"></i> 見た人</dt>
+      <dt><i class="fa fa-paw"></i> {{ t('Seen by') }}</dt>
       <dd>
         <div id="seen-user-list" data-seen-users="{{ page.seenUsers|default([])|join(',') }}"></div>
       </dd>

+ 1 - 0
locales/en

@@ -0,0 +1 @@
+en-US

+ 166 - 0
locales/en-US/translation.json

@@ -0,0 +1,166 @@
+{
+  "Help": "Help",
+  "Edit": "Edit",
+  "Delete": "Delete",
+  "Move": "Move",
+  "Moved": "Moved",
+  "Like!": "Like!",
+  "Seen by": "Seen by",
+  "Cancel": "Cancel",
+  "Create": "Create",
+  "Admin": "Admin",
+  "New": "New",
+
+  "Update": "Update",
+  "Update Page": "Update Page",
+  "Warning": "Warning",
+
+  "Sign in": "Sign in",
+  "Sign up is here": "Sign up",
+  "Sign in is here": "Sign in",
+  "Sign up": "Sign up",
+  "Sign up with Google Account": "Sign up with Google Account",
+  "Sign in with Google Account": "Sign in with Google Account",
+  "Sign up with this Google Account": "Sign up with this Google Account",
+  "Example": "Example",
+  "Taro Yamada": "James Bond",
+
+  "List View": "List",
+  "Timeline View": "Timeline",
+  "Presentation Mode": "Presentation Mode",
+
+  "Created": "Created",
+  "Last updated": "Updated",
+
+  "Share": "Share",
+  "Share Link": "Share Link",
+  "Markdown Link": "Markdown Link",
+
+  "Unportalize": "Unportalize",
+
+  "View this version": "View this version",
+  "View diff": "View diff",
+
+  "User ID": "User ID",
+  "User Settings": "User Settings",
+  "User Information": "User Information",
+  "Basic Info": "Basic Info",
+  "Name": "Name",
+  "Email": "Email",
+  "Language": "Language",
+  "English": "English",
+  "Japanese": "Japanese",
+  "Set Profile Image": "Set Profile Image",
+  "Current Image": "Current Image",
+  "Delete Image": "Delete Image",
+  "Delete this image?": "Delete this image?",
+  "Updated": "Updated",
+  "Upload new image": "Upload new image",
+  "Google Setting": "Google Setting",
+  "Connected": "Connected",
+  "Disconnect": "Disconnect",
+
+  "Create today's": "Create today's ...",
+  "Memo": "memo",
+  "Input page name": "Input page name",
+  "Input page name (optional)": "Input page name (optional)",
+  "New Page": "New Page",
+  "Create under": "Create page under: <code>%s</code>",
+
+  "Table of Contents": "Table of Contents",
+
+  "Public": "Public",
+  "Anyone with the link": "Anyone with the link",
+  "Specified users only": "Specified users only",
+  "Just me": "Just me",
+  "Shareable link": "Shareable link",
+
+  "Show latest": "Show latest",
+  "Load latest": "Load latest",
+  "edited this page": "edited this page.",
+
+  "Deleted Pages": "Deleted Pages",
+  "Sign out": "Logout",
+
+  "page_register": {
+    "notice": {
+      "restricted": "Admin approval required.",
+      "restricted_defail": "Once the admin approves your sign up, you'll be able to access this wiki.",
+      "google_account_continue": "Enter your user ID, name and password to continue."
+    },
+    "form_help": {
+      "email": "You must have email address which listed below to sign up to this wiki.",
+      "password": "Your password must be at least 6 characters long.",
+      "user_id": "The URL of pages you create will contain your User ID. Your User ID can consist of letters, numbers, and some symbols."
+    }
+  },
+
+  "page_me": {
+    "form_help": {
+      "profile_image1": "Image upload settings not completed.",
+      "profile_image2": "Set up AWS or enable local uploads.",
+      "google_connect1": "With Google Connect, you can sign in with your Google Account.",
+      "google_connect2": "Only Google Apps accounts with the following email addresses are connectable Google accounts:",
+      "google_disconnect1": "If you disconnect your Google account, you will be unable to sign in using Google Authentication",
+      "google_disconnect2": "After disconnecting your Google account, you can sign in normally using your email and password"
+    }
+  },
+  "page_me_apitoken": {
+    "notice": {
+      "apitoken_issued": "API Token is not issued.",
+      "update_token1": "You can update to generate a new API Token.",
+      "update_token2": "You will need to update the API Token in any existing processes."
+    },
+    "form_help": {
+    }
+  },
+
+  "Password": "Password",
+  "Password Settings": "Password Settings",
+  "Set new Password": "Set new Password",
+  "Update Password": "Update Password",
+  "Current password": "Current password",
+  "New password": "New password",
+  "Re-enter new password": "Re-enter new password",
+  "Please set a password": "Please set a password",
+  "You can sign in with email and password": "You can sign in with <code>%s</code> and password",
+
+  "API Settings": "API Settings",
+  "API Token Settings": "API Token Settings",
+  "Current API Token": "Current API Token",
+  "Update API Token": "Update API Token",
+
+  "page_page": {
+      "notice": {
+          "version": "This is not the current version.",
+          "moved": "This page was moved from <code>%s</code>",
+          "restricted": "Access to this page is restricted"
+      }
+  },
+
+  "Rename page": "Rename page",
+  "New page name": "New page name",
+  "Current page name": "Current page name",
+  "Redirect": "Redirect",
+  "modal_rename": {
+    "help": {
+      "redirect": "Redirect to new page if someone accesses <code>%s</code>"
+    }
+  },
+
+  "modal_help": {
+      "basic": {
+          "title": "Basics",
+          "body1": "There are 2 types of pages: List pages (showing lists of links to other pages) and normal pages.",
+          "body2": "Pages that end with a slash / are List pages for anything following the slash.",
+          "body3": "You can view older versions of a page from the History tab. Any changes made will be stored here."
+      },
+      "tips": {
+          "title": "Quick Tips on Editing",
+          "body1": "Use sections and subsections to make it easier for your friends to read your pages."
+      },
+      "markdown": {
+          "title": "Markdown Rules"
+      }
+  }
+}

+ 166 - 0
locales/ja/translation.json

@@ -0,0 +1,166 @@
+{
+  "Help": "ヘルプ",
+  "Edit": "編集",
+  "Delete": "削除",
+  "Move": "移動",
+  "Moved": "移動しました",
+  "Like!": "いいね!",
+  "Seen by": "見た人",
+  "Cancel": "キャンセル",
+  "Create": "作成",
+  "Admin": "管理",
+  "New": "作成",
+
+  "Update": "更新",
+  "Update Page": "ページを更新",
+  "Warning": "注意",
+
+  "Sign in": "ログイン",
+  "Sign up is here": "新規登録はこちら",
+  "Sign in is here": "ログインはこちら",
+  "Sign up": "新規登録",
+  "Sign up with Google Account": "Google で登録",
+  "Sign in with Google Account": "Google でログイン",
+  "Sign up with this Google Account": "この Google アカウントで登録します",
+  "Example": "例",
+  "Taro Yamada": "山田 太郎",
+
+  "List View": "リスト表示",
+  "Timeline View": "タイムライン表示",
+  "Presentation Mode": "プレゼンモード",
+
+  "Created": "作成日",
+  "Last updated": "最終更新",
+
+  "Share": "共有",
+  "Share Link": "共有用リンク",
+  "Markdown Link": "Markdown形式のリンク",
+
+  "Unportalize": "ポータル解除",
+
+  "View this version": "このバージョンを見る",
+  "View diff": "差分を見る",
+
+  "User ID": "ユーザーID",
+  "User Settings": "ユーザー設定",
+  "User Information": "ユーザー情報",
+  "Basic Info": "ユーザーの基本情報",
+  "Name": "名前",
+  "Email": "メールアドレス",
+  "Language": "言語",
+  "English": "英語",
+  "Japanese": "日本語",
+  "Set Profile Image": "プロフィール画像の設定",
+  "Current Image": "現在の画像",
+  "Delete Image": "画像を削除",
+  "Delete this image?": "削除してよろしいですか?",
+  "Updated": "更新しました",
+  "Upload new image": "新しい画像をアップロード",
+  "Google Setting": "Google設定",
+  "Connected": "接続されています",
+  "Disconnect": "接続を解除",
+
+  "Create today's": "今日の◯◯を作成",
+  "Memo": "メモ",
+  "Input page name": "ページ名を入力",
+  "Input page name (optional)": "ページ名を入力(空欄OK)",
+  "New Page": "新規ページ",
+  "Create under": "<code>%s</code>以下に作成",
+
+  "Table of Contents": "目次",
+
+  "Public": "公開",
+  "Anyone with the link": "リンクを知っている人のみ",
+  "Specified users": "特定ユーザーのみ",
+  "Just me": "自分のみ",
+  "Shareable link": "このページの共有用URL",
+
+  "Show latest": "最新のページを表示",
+  "Load latest": "最新版を読み込む",
+  "edited this page": "さんがこのページを編集しました。",
+
+  "Deleted Pages": "削除済みページ",
+  "Sign out": "ログアウト",
+
+  "page_register": {
+    "notice": {
+       "restricted": "この Wiki への新規登録は制限されています。",
+       "restricted_defail": "利用を開始するには、新規登録後、管理者による承認が必要です。",
+       "google_account_continue": "ユーザーID、名前、パスワードを決めて登録を継続してください。"
+    },
+    "form_help": {
+      "email": "この Wiki では以下のメールアドレスのみ登録可能です。",
+      "password": "パスワードには、6文字以上の半角英数字または記号等を設定してください。",
+      "user_id": "ユーザーIDは、ユーザーページのURLなどに利用されます。半角英数字と一部の記号のみ利用できます。"
+    }
+  },
+
+  "page_me": {
+    "form_help": {
+      "profile_image1": "画像をアップロードをするための設定がされていません。",
+      "profile_image2": "アップロードできるようにするには、AWS またはローカルアップロードの設定をしてください。",
+      "google_connect1": "Googleコネクトをすると、Googleアカウントでログイン可能になります。",
+      "google_connect2": "コネクト可能なGoogleアカウントは、以下のメールアドレスの発行できるGoogle Appsアカウントに限られます。",
+      "google_disconnect1": "接続を解除すると、Googleでログインができなくなります。",
+      "google_disconnect2": "解除後はメールアドレスとパスワードでログインすることができます。"
+    }
+  },
+  "page_me_apitoken": {
+    "notice": {
+      "apitoken_issued": "API Token が設定されていません。",
+      "update_token1": "API Token を更新すると、自動的に新しい Token が生成されます。",
+      "update_token2": "現在の Token を利用している処理は動かなくなります。"
+    },
+    "form_help": {
+    }
+  },
+
+  "Password": "パスワード",
+  "Password Settings": "パスワード設定",
+  "Set new Password": "パスワードを新規に設定",
+  "Update Password": "パスワードを更新",
+  "Current password": "現在のパスワード",
+  "New password": "新しいパスワード",
+  "Re-enter new password": "(確認用)",
+  "Please set a password": "パスワードを設定してください",
+  "You can sign in with email and password": "<code>%s</code> と設定されたパスワードの組み合わせでログイン可能になります。",
+
+  "API Settings": "API設定",
+  "API Token Settings": "API Token設定",
+  "Current API Token": "現在のAPI Token",
+  "Update API Token": "API Tokenを更新",
+
+  "page_page": {
+      "notice": {
+          "version": "これは現在の版ではありません。",
+          "moved": "このページは <code>%s</code> から移動しました。",
+          "restricted": "このページの閲覧は制限されています"
+      }
+  },
+
+  "Rename page": "ページを移動する",
+  "New page name": "移動先のページ名",
+  "Current page name": "現在のページ名",
+  "Redirect": "リダイレクトする",
+  "modal_rename": {
+    "help": {
+      "redirect": "チェックを入れると、<code>%s</code>にアクセスされた際に自動的に新しいページにジャンプします。"
+    }
+  },
+
+  "modal_help": {
+      "basic": {
+          "title": "基本的な機能",
+          "body1": "表示される画面には、「一覧ページ」と「ページ」の2種類があります",
+          "body2": "スラッシュ <code>/</code> で終わるページは、その階層の一覧ページとなります。",
+          "body3": "ページでの変更はすべて記録されています。History からそのページの過去の状態を見ることができます。"
+      },
+      "tips": {
+          "title": "編集のコツ",
+          "body1": "文章の <strong>構造</strong> を意識しましょう。本を書くように、内容と文脈を整理してセクション・サブセクション...と構造的に書くと、わかりやすく他人に伝わりやすいページがになります。"
+      },
+      "markdown": {
+          "title": "記法"
+      }
+  }
+}

+ 4 - 2
resource/js/crowi.js

@@ -271,7 +271,9 @@ $(function() {
         var page = res.page;
 
         $('#newPageNameCheck').removeClass('alert-danger');
-        $('#newPageNameCheck').html('<img src="/images/loading_s.gif"> 移動しました。移動先にジャンプします。');
+        //$('#newPageNameCheck').html('<img src="/images/loading_s.gif"> 移動しました。移動先にジャンプします。');
+        // fix
+        $('#newPageNameCheck').html('<img src="/images/loading_s.gif"> Page moved! Redirecting to new page location.');
 
         setTimeout(function() {
           top.location.href = page.path + '?renamed=' + pagePath;
@@ -404,7 +406,7 @@ $(function() {
 
     $.getJSON('/_api/check_username', {username: username}, function(json) {
       if (!json.valid) {
-        $('#help-block-username').html('<i class="fa fa-warning"></i>このユーザーIDは利用できません。<br>');
+        $('#help-block-username').html('<i class="fa fa-warning"></i> This User ID is not available.<br>');
         $('#input-group-username').addClass('has-error');
       }
     });

+ 1 - 1
test/models/user.test.js

@@ -14,7 +14,7 @@ describe('User', function () {
   describe('Create and Find.', function () {
     context('The user', function() {
       it('should created', function(done) {
-        User.createUserByEmailAndPassword('Aoi Miyazaki', 'aoi', 'aoi@example.com', 'hogefuga11', function (err, userData) {
+        User.createUserByEmailAndPassword('Aoi Miyazaki', 'aoi', 'aoi@example.com', 'hogefuga11', 'en', function (err, userData) {
           expect(err).to.be.null;
           expect(userData).to.instanceof(User);
           done();