├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── README_zh.md ├── index.js ├── lib ├── helpers.js └── i18n.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | tmp/ 3 | coverage/ 4 | *.log 5 | .jshintrc 6 | .travis.yml 7 | gulpfile.js 8 | .idea/ 9 | appveyor.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node-js: 3 | - "4" 4 | 5 | cache: 6 | apt: true 7 | directories: 8 | - node_modules 9 | 10 | before_install: 11 | - npm install lodash --save 12 | 13 | script: 14 | - npm test 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jamling 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hexo-generator-i18n 2 | 3 | [![Build Status](https://travis-ci.org/Jamling/hexo-generator-i18n.svg?branch=master)](https://travis-ci.org/Jamling/hexo-generator-i18n) 4 | [![node](https://img.shields.io/node/v/hexo-generator-i18n.svg)](https://www.npmjs.com/package/hexo-generator-i18n) 5 | [![npm downloads](https://img.shields.io/npm/dt/hexo-generator-i18n.svg)](https://www.npmjs.com/package/hexo-generator-i18n) 6 | [![npm version](https://img.shields.io/npm/v/hexo-generator-i18n.svg)](https://www.npmjs.com/package/hexo-generator-i18n) 7 | [![GitHub release](https://img.shields.io/github/release/jamling/hexo-generator-i18n.svg)](https://github.com/Jamling/hexo-generator-i18n/releases/latest) 8 | 9 | Multi-languages pages generator for [Hexo]. Here is a [demo](https://www.ieclipse.cn/other/) 10 | 11 | ## Installation 12 | 13 | ``` bash 14 | $ npm install hexo-generator-i18n --save 15 | ``` 16 | 17 | ## Config 18 | 19 | _config.yml 20 | ``` yaml 21 | # hexo default is empty, change to exact languages, and add xx.yml under your theme languages dir. 22 | language: [zh,en] 23 | # config hexo-generator-i18n option (optional, this is default option) 24 | i18n: 25 | type: [page, post] 26 | generator: [archive, category, tag, index] 27 | ``` 28 | 29 | - **type**: What type of model to be i18n generated 30 | - page: All page model under source 31 | - post: All post model under source/_post 32 | - **generator**: Which generator to be i18n generated, it's array of your installed generator names. 33 | - archive: Generate i18n archive page 34 | - category: Generate i18n category page 35 | - tag: Generate i18n tag page 36 | - index: Generate i18n index page 37 | 38 | ***And add xx.yml (such as zh.yml, en.yml) under your themes languages directory*** 39 | 40 | Set language display name in source/_data/languages.yml (Optional) 41 | ```yaml 42 | zh: 简体中文 43 | en: English 44 | ``` 45 | 46 | ## Order 47 | 48 | Make sure this plugin after other generators in dependencies of package.json 49 | 50 | ## Usage 51 | You may need to replace `url_for()` to `url_for_lang()` to output link under current language. Usually, in post/archive/tag/category pages. 52 | 53 | ## Helpers 54 | 55 | ### get_langs 56 | Return array of config languages, exclude `default` language 57 | 58 | ### default_lang 59 | Return default language, it's the first element of [get_langs](#get_langs) 60 | 61 | ### switch_lang 62 | Return the absolute url under lang 63 | ```js 64 | window.location = {{ switch_lang('en')}} 65 | ``` 66 | 67 | ### url_for_lang 68 | Return url for path with language 69 | ```js 70 | ret += ''; 75 | ``` 76 | 77 | ## License 78 | 79 | MIT 80 | 81 | [Hexo]: http://hexo.io/ 82 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | [![Build Status](https://travis-ci.org/Jamling/hexo-generator-i18n.svg?branch=master)](https://travis-ci.org/Jamling/hexo-generator-i18n) 4 | [![node](https://img.shields.io/node/v/hexo-generator-i18n.svg)](https://www.npmjs.com/package/hexo-generator-i18n) 5 | [![npm downloads](https://img.shields.io/npm/dt/hexo-generator-i18n.svg)](https://www.npmjs.com/package/hexo-generator-i18n) 6 | [![npm version](https://img.shields.io/npm/v/hexo-generator-i18n.svg)](https://www.npmjs.com/package/hexo-generator-i18n) 7 | [![GitHub release](https://img.shields.io/github/release/jamling/hexo-generator-i18n.svg)](https://github.com/Jamling/hexo-generator-i18n/releases/latest) 8 | 9 | [Hexo]国际化站点生成插件. 10 | 11 | ## 安装 12 | 13 | ``` bash 14 | $ npm install hexo-generator-i18n --save 15 | ``` 16 | 17 | ## 设置 18 | 19 | _config.yml 20 | ``` yaml 21 | # 需修改Hexo默认的空值为确切的语言列表,记得在主题languages目录下添加对应的语言.yml文件 22 | language: [zh,en] 23 | # hexo-generator-i18n 选项(可选,默认使用如下设置) 24 | i18n: 25 | type: [page, post] 26 | generator: [archive, category, tag, index] 27 | ``` 28 | 29 | - **type**: 想要生成国际化页面类型 30 | - page: source目录下的所有page页面 31 | - post: source/_post目录下所有的post页面 32 | - **generator**: 设置需要国际化的其它生成器。 33 | - archive: 生成国际化归档页 34 | - category: 生成国际化分类页 35 | - tag: 生成国际化标签页 36 | - index: 生成国际化首页 37 | 38 | ***在主题languages目录下添加对应的语言.yml文件(如zh.yml, en.yml)*** 39 | 40 | 在source/_data/languages.yml中设置语言的显示名称(Optional) 41 | ```yaml 42 | zh: 简体中文 43 | en: English 44 | ``` 45 | 46 | ## 顺序 47 | 48 | 确保i18n插件位于package.jsondependencies配置的其它生成插件之后。不然有可能导致找不到生成器错误。 49 | 50 | ## 使用 51 | 在您的主题中,您可能需要将`url_for()`辅助函数替换为`url_for_lang()`以确保点击链接时,仍然保持当前用户所选的语言。 52 | 53 | ## 辅助函数 54 | 55 | ### get_langs 56 | - Return Array 57 | 58 | 返回配置的语言列表(数组), 返回的列表不包含`default`。 59 | 60 | ### default_lang 61 | - Return string 62 | 63 | 返回默认语言,为[get_langs](#get_langs)的第一项。 64 | 65 | ### switch_lang 66 | - Param lang 67 | - Return string 68 | 返回其它语言下的当前页面URL。 69 | ```js 70 | window.location = {{ switch_lang('en')}} 71 | ``` 72 | 73 | ### url_for_lang 74 | - Param path 75 | - Param lang 76 | - Return string 77 | 78 | 返回带语言的路径URL 79 | ```js 80 | ret += ''; 85 | ``` 86 | 87 | ## License 88 | 89 | MIT 90 | 91 | [Hexo]: http://hexo.io/ 92 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | //------------> Init langs 6 | if (!hexo.config.i18n) { 7 | hexo.log.info('i18n not config in _config.yml, use default config!\nPlease visit https://github.com/Jamling/hexo-generator-i18n for more information'); 8 | hexo.config.i18n = { 9 | type: ["page", "post"], 10 | generator: ["archive", "category", "tag", "index"] 11 | } 12 | } 13 | if (!hexo.config.i18n.languages){ 14 | var languages = hexo.config.language; 15 | if(!Array.isArray(languages)){ 16 | languages = [languages]; 17 | } 18 | _.pull(languages, 'default'); 19 | hexo.config.i18n.languages = languages; 20 | } 21 | 22 | //------------> Helper 23 | var helper = require('./lib/helpers'); 24 | 25 | hexo.extend.helper.register('get_langs', helper.langs); 26 | hexo.extend.helper.register('default_lang', helper.defaultLang); 27 | hexo.extend.helper.register('switch_lang', helper.switchLang); 28 | hexo.extend.helper.register('url_for_lang', helper.url); 29 | 30 | //------------> Generator 31 | var i18n = require('./lib/i18n'); 32 | var type = hexo.config.i18n.type; 33 | if (type){ 34 | if(!Array.isArray(type)){ 35 | type = [type]; 36 | } 37 | 38 | type.forEach(function(item){ 39 | if (item == 'page') { 40 | hexo.extend.generator.register('page-i18n', i18n.page); 41 | } 42 | else if (item == 'post') { 43 | hexo.extend.generator.register('post-i18n', i18n.post); 44 | } 45 | }); 46 | } 47 | 48 | var generator = hexo.config.i18n.generator; 49 | if (generator){ 50 | if(!Array.isArray(generator)){ 51 | generator = [generator]; 52 | } 53 | hexo.config.i18n.generator = generator; 54 | 55 | var gs = hexo.extend.generator.list(); 56 | generator.forEach(function(item){ 57 | var g = gs[item]; 58 | if (!g && !_.endsWith(item, '-i18n')){ 59 | _.pull(hexo.config.i18n.generator, item); 60 | } 61 | }); 62 | hexo.extend.generator.register('other-i18n', i18n.archive); 63 | } 64 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | function getLanguages(){ 6 | var langs = this.config.i18n.languages; 7 | if (!langs){ 8 | var languages = hexo.config.language; 9 | if(!Array.isArray(languages)){ 10 | languages = [languages]; 11 | } 12 | _.pull(languages, 'default'); 13 | this.config.i18n.languages = languages; 14 | } 15 | return this.config.i18n.languages; 16 | } 17 | 18 | function defaultLang(){ 19 | return this.get_langs()[0]; 20 | } 21 | 22 | function switchLang(lang){ 23 | var l = this.get_langs(); 24 | var p = this.page.path; 25 | var root = this.config.root || ''; 26 | if (_.startsWith(p, this.page.lang)) { 27 | p = p.substring(this.page.lang.length); 28 | } 29 | if (!_.startsWith(p, '/')){ 30 | p = '/' + p; 31 | } 32 | var ret = ''; 33 | if (l.indexOf(lang) == 0) { 34 | ret = root + p.substring(1); 35 | } else { 36 | ret = root + lang + p; 37 | } 38 | return ret; 39 | } 40 | 41 | function i18n_url(path, language) { 42 | var root = this.config.root || ''; 43 | var lang = language ? language : this.page.lang; 44 | var url = this.url_for(path); 45 | 46 | // ignore from url_for. 47 | if (url === '#' || _.startsWith(url, '//') || _.includes(url, '://') || _.startsWith(url, 'mailto:')) { 48 | return url; 49 | } 50 | 51 | if (!_.startsWith(url, '/')){ 52 | url = '/' + url; 53 | } 54 | 55 | var relativeUrl = url.replace(root, '/'); 56 | var pathLang = relativeUrl.split('/')[1]; 57 | var languages = this.get_langs(); 58 | 59 | if (languages.indexOf(pathLang) != -1){ 60 | return url; 61 | } 62 | 63 | if (lang && lang !== languages[0]){ 64 | url = root + lang + relativeUrl; 65 | } 66 | 67 | return url; 68 | } 69 | 70 | exports.langs = getLanguages; 71 | exports.defaultLang = defaultLang; 72 | exports.switchLang = switchLang; 73 | exports.url = i18n_url; 74 | -------------------------------------------------------------------------------- /lib/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | function getLang(hexo){ 6 | if (!hexo.config.i18n.languages){ 7 | var languages = hexo.config.language; 8 | if(!Array.isArray(languages)){ 9 | languages = [languages]; 10 | } 11 | _.pull(languages, 'default'); 12 | hexo.config.i18n.languages = languages; 13 | } 14 | return hexo.config.i18n.languages; 15 | } 16 | 17 | function i18n_page(locals) { 18 | var _self = this; 19 | var languages = getLang(_self); 20 | 21 | var i18n = []; 22 | var langPaths = []; 23 | 24 | locals.pages.forEach(function(page){ 25 | var lang = page.path.split('/')[0]; 26 | if (languages.indexOf(lang) == -1){ 27 | i18n.push(page); 28 | } else { 29 | langPaths.push(page.path); 30 | page.lang = lang; 31 | } 32 | }); 33 | 34 | var result = []; 35 | i18n.forEach(function(page){ 36 | var layouts = ['page', 'post', 'index']; 37 | var layout = page.layout; 38 | for (var i = 1; i< languages.length; i++){ 39 | var l = languages[i]; 40 | var path = l + '/' + page.path; 41 | if (langPaths.indexOf(path) != -1){ 42 | continue; 43 | } 44 | if (!layout || layout === 'false' || layout === 'off') { 45 | result.push({path: path, data: page.content}); 46 | } 47 | else { 48 | if (layout !== 'page') layouts.unshift(layout); 49 | var copy = {}; 50 | if (languages.length <= 2) { 51 | _.extend(copy, page); 52 | } else { 53 | _.defaults(copy, page); 54 | } 55 | copy.lang = l; 56 | copy.__page = true; 57 | copy.path = path; 58 | _self.log.debug("generate i18n page " + copy.path); 59 | result.push({ 60 | path: copy.path, 61 | layout: layouts, 62 | data: copy 63 | }); 64 | } 65 | } 66 | }); 67 | return result.length > 0 ? result : []; 68 | }; 69 | 70 | function i18n_post(locals) { 71 | var _self = this; 72 | var languages = getLang(_self); 73 | 74 | var i18n = []; 75 | var langPaths = []; 76 | 77 | locals.posts.forEach(function(page){ 78 | var lang = page.path.split('/')[0]; 79 | if (languages.indexOf(lang) == -1){ 80 | i18n.push(page); 81 | }else { 82 | langPaths.push(page.path); 83 | page.lang = lang; 84 | } 85 | }); 86 | 87 | var result = []; 88 | i18n.forEach(function(page){ 89 | var layouts = ['post', 'page', 'index']; 90 | var layout = page.layout; 91 | for (var i = 1; i< languages.length; i++){ 92 | var l = languages[i]; 93 | var path = l + '/' + page.path; 94 | if (langPaths.indexOf(path) != -1){ 95 | continue; 96 | } 97 | if (!layout || layout === 'false' || layout === 'off') { 98 | result.push({path: path, data: page.content}); 99 | } 100 | else { 101 | if (layout !== 'post') layouts.unshift(layout); 102 | var copy = {}; 103 | if (languages.length <= 2) { 104 | _.extend(copy, page); 105 | } else { 106 | _.defaults(copy, page); 107 | } 108 | copy.lang = l; 109 | copy.__post = true; 110 | copy.path = path; 111 | _self.log.debug("generate i18n post " + copy.path); 112 | result.push({ 113 | path: copy.path, 114 | layout: layouts, 115 | data: copy 116 | }); 117 | } 118 | } 119 | }); 120 | return result.length > 0 ? result : []; 121 | }; 122 | 123 | function i18n_archive(locals) { 124 | var _self = this; 125 | var languages = getLang(_self); 126 | 127 | var i18n = []; 128 | var langPaths = []; 129 | 130 | var result = []; 131 | var gs = _self.extend.generator.list(); 132 | var siteLocals = _self.locals.toObject(); 133 | 134 | this.config.i18n.generator.forEach(function(g){ 135 | var datas = gs[g].call(_self, siteLocals).then(function(data){ 136 | _self.log.debug('i18n for generator: %s', g); 137 | return data; 138 | }) 139 | .reduce(function(result, data) { 140 | return data ? result.concat(data) : result; 141 | }, []) 142 | .map(function(item){ 143 | for (var i = 1; i< languages.length; i++){ 144 | var l = languages[i]; 145 | 146 | var copy = {}; 147 | if (languages.length <= 2) { 148 | _.extend(copy, item); 149 | } else { 150 | _.defaultsDeep(copy, item); 151 | } 152 | copy.path = l + '/' + item.path; 153 | copy.data.base = l + '/' + item.data.base; 154 | copy.data.prev_link = l + '/' + item.data.prev_link; 155 | copy.data.current_url = l + '/' + item.data.current_url; 156 | copy.data.next_link = l + '/' + item.data.next_link; 157 | //console.log(copy); 158 | result.push(copy); 159 | } 160 | }); 161 | }); 162 | return result; 163 | }; 164 | 165 | exports.page = i18n_page; 166 | exports.post = i18n_post; 167 | exports.archive = i18n_archive; 168 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-generator-i18n", 3 | "version": "0.0.10", 4 | "description": "Multi-language pages generator for Hexo.", 5 | "main": "index", 6 | "directories": { 7 | "lib": "./lib" 8 | }, 9 | "engines": { 10 | "node": ">= 0.10.0" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Jamling/hexo-generator-i18n.git" 15 | }, 16 | "homepage": "http://ieclipse.cn/", 17 | "keywords": [ 18 | "hexo", 19 | "generator", 20 | "i18n" 21 | ], 22 | "author": { 23 | "name": "Jamling", 24 | "email": "li.jamling@gmail.com", 25 | "url": "http://ieclipse.cn" 26 | }, 27 | "license": "MIT", 28 | "devDependencies": {}, 29 | "dependencies": { 30 | "lodash": "^4.15.0" 31 | } 32 | } 33 | --------------------------------------------------------------------------------