isola 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/USAGE.ja.md +64 -0
- data/USAGE.md +64 -0
- data/lib/isola/context.rb +20 -5
- data/lib/isola/language_path_router.rb +39 -0
- data/lib/isola/site.rb +56 -13
- data/lib/isola/source.rb +4 -2
- data/lib/isola/version.rb +1 -1
- data/lib/isola.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c9ea7b839acee149cdc001dae2606001e8393efebafb19bcbea8852a694bc7a0
|
|
4
|
+
data.tar.gz: 95435cd787ea492043c2b8f0f62b8585e3265f4e7d5f6f315e1705e322700938
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e80d3f35ca2f20a8d449fd224e2b3f580c3d32161e3d8ec0259711fe5be6438f28d5f0c89dc663c693e42283b3d1673916c0ad7fe28620f444d7f77f9a30d297
|
|
7
|
+
data.tar.gz: 289d16edbc4e8c3e9762015e6088a15e34a88af9c4874ddeba90bee78c2457cdabf15708fcc81c1058b468732022941f0f5b99c081557f6032dc1c3ae57ab416
|
data/.rubocop.yml
ADDED
data/USAGE.ja.md
CHANGED
|
@@ -116,3 +116,67 @@ Isolaが処理するテンプレートエンジンは現在 **ERB**(`.erb`)
|
|
|
116
116
|
| `*.css`, `*.js` など | なし | そのままコピー |
|
|
117
117
|
|
|
118
118
|
`_`や`.`で始まるファイル・ディレクトリは自動的に除外されます(`_layouts/`と`_includes/`を除く)。
|
|
119
|
+
|
|
120
|
+
## 多言語対応
|
|
121
|
+
|
|
122
|
+
Isolaは多言語サイトの構築をサポートしています。デフォルト言語のページはサイトルートに、その他の言語は言語コード名のサブディレクトリに配置します。
|
|
123
|
+
|
|
124
|
+
### 設定
|
|
125
|
+
|
|
126
|
+
`_config.yaml`に`default_language`と`languages`を追加します:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
default_language: ja
|
|
130
|
+
languages:
|
|
131
|
+
ja:
|
|
132
|
+
label: "日本語"
|
|
133
|
+
en:
|
|
134
|
+
label: "English"
|
|
135
|
+
title: "My Site (EN)"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
各言語エントリでサイトレベルの設定値を上書きできます。例えば上記の設定では、英語ページのレンダリング時に`site[:title]`が`"My Site (EN)"`を返します。
|
|
139
|
+
|
|
140
|
+
### ディレクトリ構成
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
my-site/
|
|
144
|
+
├── _config.yaml
|
|
145
|
+
├── _layouts/
|
|
146
|
+
│ ├── default.html.erb # 共有レイアウト(全言語で使用)
|
|
147
|
+
│ └── en/default.html.erb # 英語専用レイアウト(上書き)
|
|
148
|
+
├── _includes/
|
|
149
|
+
│ ├── head.html.erb # 共有インクルード
|
|
150
|
+
│ └── en/head.html.erb # 英語専用インクルード(上書き)
|
|
151
|
+
├── index.md # デフォルト言語(ja)のページ
|
|
152
|
+
└── en/index.md # 英語ページ
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- **ページ**: デフォルト言語のページはルートに配置します。他の言語は`<lang>/`サブディレクトリに配置します(例: `en/index.md`)。
|
|
156
|
+
- **レイアウトとインクルード**: 言語固有のオーバーライドは`_layouts/<lang>/`や`_includes/<lang>/`に配置します。言語固有のバージョンが見つからない場合、共有バージョンがフォールバックとして使われます。
|
|
157
|
+
|
|
158
|
+
### テンプレート変数
|
|
159
|
+
|
|
160
|
+
標準のテンプレート変数に加え、多言語サイトでは以下が利用できます:
|
|
161
|
+
|
|
162
|
+
- `page[:lang]` — 現在のページの言語(例: `:ja`, `:en`)
|
|
163
|
+
- `page[:translations]` — 現在のページの全翻訳版を`{lang: url_path}`形式のハッシュで返す(先頭が`/`のURLパスであり、`href`属性などにそのまま利用できます)
|
|
164
|
+
|
|
165
|
+
#### hreflangリンクの生成
|
|
166
|
+
|
|
167
|
+
`page[:translations]`を使って代替言語リンクを出力できます:
|
|
168
|
+
|
|
169
|
+
```erb
|
|
170
|
+
<% page[:translations].each do |lang, url| %>
|
|
171
|
+
<link rel="alternate" hreflang="<%= lang %>" href="<%= url %>">
|
|
172
|
+
<% end %>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 出力
|
|
176
|
+
|
|
177
|
+
出力はソースの構成を反映します:
|
|
178
|
+
|
|
179
|
+
| ソース | 出力 |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `index.md` | `_site/index.html` |
|
|
182
|
+
| `en/index.md` | `_site/en/index.html` |
|
data/USAGE.md
CHANGED
|
@@ -116,3 +116,67 @@ If an extension remains after processing, it is kept as-is. If no extension rema
|
|
|
116
116
|
| `*.css`, `*.js` etc. | None | Copied as-is |
|
|
117
117
|
|
|
118
118
|
Files and directories starting with `_` or `.` are excluded automatically (except `_layouts/` and `_includes/`).
|
|
119
|
+
|
|
120
|
+
## Multi-Language Support
|
|
121
|
+
|
|
122
|
+
Isola supports building multi-language sites. Pages for the default language live at the site root, while other languages are placed in subdirectories named by language code.
|
|
123
|
+
|
|
124
|
+
### Configuration
|
|
125
|
+
|
|
126
|
+
Add `default_language` and `languages` to `_config.yaml`:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
default_language: ja
|
|
130
|
+
languages:
|
|
131
|
+
ja:
|
|
132
|
+
label: "日本語"
|
|
133
|
+
en:
|
|
134
|
+
label: "English"
|
|
135
|
+
title: "My Site (EN)"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Each language entry can override any site-level configuration value. For example, `site[:title]` returns `"My Site (EN)"` when rendering English pages.
|
|
139
|
+
|
|
140
|
+
### Directory Structure
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
my-site/
|
|
144
|
+
├── _config.yaml
|
|
145
|
+
├── _layouts/
|
|
146
|
+
│ ├── default.html.erb # Shared layout (used by all languages)
|
|
147
|
+
│ └── en/default.html.erb # English-specific layout override
|
|
148
|
+
├── _includes/
|
|
149
|
+
│ ├── head.html.erb # Shared include
|
|
150
|
+
│ └── en/head.html.erb # English-specific include override
|
|
151
|
+
├── index.md # Default language (ja) page
|
|
152
|
+
└── en/index.md # English page
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- **Pages**: Default-language pages live at the root. Other languages go in `<lang>/` subdirectories (e.g. `en/index.md`).
|
|
156
|
+
- **Layouts and includes**: Place language-specific overrides under `_layouts/<lang>/` or `_includes/<lang>/`. If a language-specific version is not found, the shared version is used as a fallback.
|
|
157
|
+
|
|
158
|
+
### Template Variables
|
|
159
|
+
|
|
160
|
+
In addition to the standard template variables, multi-language sites provide:
|
|
161
|
+
|
|
162
|
+
- `page[:lang]` — the language of the current page (e.g. `:ja`, `:en`)
|
|
163
|
+
- `page[:translations]` — a hash of `{lang: url_path}` (URL paths starting with `/`, suitable for `href` attributes) for all available translations of the current page
|
|
164
|
+
|
|
165
|
+
#### Generating hreflang Links
|
|
166
|
+
|
|
167
|
+
Use `page[:translations]` to output alternate-language links:
|
|
168
|
+
|
|
169
|
+
```erb
|
|
170
|
+
<% page[:translations].each do |lang, url| %>
|
|
171
|
+
<link rel="alternate" hreflang="<%= lang %>" href="<%= url %>">
|
|
172
|
+
<% end %>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Output
|
|
176
|
+
|
|
177
|
+
The output mirrors the source structure:
|
|
178
|
+
|
|
179
|
+
| Source | Output |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `index.md` | `_site/index.html` |
|
|
182
|
+
| `en/index.md` | `_site/en/index.html` |
|
data/lib/isola/context.rb
CHANGED
|
@@ -2,21 +2,36 @@ require "tilt"
|
|
|
2
2
|
|
|
3
3
|
module Isola
|
|
4
4
|
class Context
|
|
5
|
-
attr_reader :
|
|
6
|
-
def initialize(source, site)
|
|
5
|
+
attr_reader :content, :layout
|
|
6
|
+
def initialize(source, site, languages: {})
|
|
7
7
|
@source = source
|
|
8
|
-
@meta =
|
|
8
|
+
@meta = source.meta.freeze
|
|
9
9
|
@site = site
|
|
10
10
|
@content = ""
|
|
11
11
|
@layout = {}
|
|
12
|
+
|
|
13
|
+
site_config = @site.config.merge(languages[@source[:lang]] || {})
|
|
14
|
+
@site_proxy = SimpleDelegator.new(@site).tap do
|
|
15
|
+
it.define_singleton_method(:[]) do |key|
|
|
16
|
+
site_config[key]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
12
19
|
end
|
|
13
20
|
|
|
14
21
|
def page
|
|
15
22
|
@meta
|
|
16
23
|
end
|
|
17
24
|
|
|
25
|
+
def site
|
|
26
|
+
@site_proxy
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def lang_path path
|
|
30
|
+
@site_proxy.url_path_for_lang(path, @source[:lang])
|
|
31
|
+
end
|
|
32
|
+
|
|
18
33
|
def include name, params = {}
|
|
19
|
-
i = @site.include name
|
|
34
|
+
i = @site.include name, lang: @source[:lang]
|
|
20
35
|
raise "include #{name} not found in #{@current.filepath}" unless i
|
|
21
36
|
i.render(self, @site, params)[0]
|
|
22
37
|
end
|
|
@@ -25,7 +40,7 @@ module Isola
|
|
|
25
40
|
@current = @source
|
|
26
41
|
@content, path = @source.render(self, @site)
|
|
27
42
|
while @current.meta[:layout]
|
|
28
|
-
layout = site.layout(@current.meta[:layout])
|
|
43
|
+
layout = site.layout(@current.meta[:layout], lang: @source[:lang])
|
|
29
44
|
raise "#{@current.meta[:layout]} not found for #{@current.filepath}" unless layout
|
|
30
45
|
@current = layout
|
|
31
46
|
@content, _ = @current.render(self, @site)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Isola
|
|
2
|
+
class LanguagePathRouter
|
|
3
|
+
def initialize(default_language:, languages:)
|
|
4
|
+
@default_language = default_language.to_s
|
|
5
|
+
@languages = languages.map(&:to_s)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def language_for(path)
|
|
9
|
+
p = Pathname(path).each_filename.to_a
|
|
10
|
+
if p.length > 1 && @languages.include?(p[0])
|
|
11
|
+
p[0].to_sym
|
|
12
|
+
else
|
|
13
|
+
@default_language.to_sym
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def canonical_path(path)
|
|
18
|
+
p = Pathname(path).each_filename.to_a
|
|
19
|
+
if p.length > 1 && @languages.include?(p[0])
|
|
20
|
+
File.join(p[1..])
|
|
21
|
+
else
|
|
22
|
+
path
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def localized_path(path, language)
|
|
27
|
+
canonical = canonical_path(path)
|
|
28
|
+
if language.to_s == @default_language
|
|
29
|
+
canonical
|
|
30
|
+
else
|
|
31
|
+
File.join(language.to_s, canonical)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def candidate_paths(path)
|
|
36
|
+
@languages.to_h { |lang| [lang.to_sym, localized_path(path, lang)] }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/isola/site.rb
CHANGED
|
@@ -1,30 +1,47 @@
|
|
|
1
1
|
require "yaml"
|
|
2
2
|
require "fileutils"
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
3
5
|
module Isola
|
|
4
6
|
class Site
|
|
5
7
|
attr_accessor :config
|
|
6
8
|
DEFAULT_CONFIG = {url: "http://example.com",
|
|
7
9
|
title: "my awesome site",
|
|
8
10
|
destination: "_site",
|
|
9
|
-
default_language:
|
|
11
|
+
default_language: :en,
|
|
10
12
|
host: "127.0.0.1",
|
|
13
|
+
languages: {},
|
|
11
14
|
port: 4444}.freeze
|
|
12
15
|
SUPPORTED_TILT_EXT = [".erb", ".md", ".markdown", ".mkd", ".html"]
|
|
13
16
|
EXT_MAP = {".md" => ".html", ".mkd" => ".html", ".markdown" => ".html", ".html" => ".html", "" => ".html"}
|
|
14
17
|
def initialize(config)
|
|
15
18
|
@config = DEFAULT_CONFIG.merge(YAML.safe_load(config, symbolize_names: true) || {})
|
|
19
|
+
@config[:default_language] = @config[:default_language].to_sym
|
|
16
20
|
@config[:root_dir] ||= Dir.pwd
|
|
17
21
|
@config[:excludes] ||= []
|
|
22
|
+
@lang_router = LanguagePathRouter.new(
|
|
23
|
+
default_language: default_language,
|
|
24
|
+
languages: languages.keys
|
|
25
|
+
)
|
|
18
26
|
collect_files
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def [] key
|
|
22
|
-
if key == :lang
|
|
23
|
-
key = :default_language
|
|
24
|
-
end
|
|
25
30
|
@config[key]
|
|
26
31
|
end
|
|
27
32
|
|
|
33
|
+
def default_language
|
|
34
|
+
@config[:default_language]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def languages
|
|
38
|
+
@config[:languages]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def language_label(lang)
|
|
42
|
+
@config[:languages][lang].to_h[:label]
|
|
43
|
+
end
|
|
44
|
+
|
|
28
45
|
def ext_to_process_with_tilt? ext
|
|
29
46
|
SUPPORTED_TILT_EXT.include? ext
|
|
30
47
|
end
|
|
@@ -58,16 +75,25 @@ module Isola
|
|
|
58
75
|
build
|
|
59
76
|
end
|
|
60
77
|
|
|
78
|
+
def url_path_for(path)
|
|
79
|
+
# will support base_url for the future.
|
|
80
|
+
File.join("/", path)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def url_path_for_lang(path, lang)
|
|
84
|
+
url_path_for(@lang_router.localized_path(path, lang))
|
|
85
|
+
end
|
|
86
|
+
|
|
61
87
|
def ignore?(path)
|
|
62
88
|
@file_handler.ignore?(path)
|
|
63
89
|
end
|
|
64
90
|
|
|
65
|
-
def layout name
|
|
66
|
-
find_entry(name, @parsed_layouts, @file_handler.layouts)
|
|
91
|
+
def layout name, lang: nil
|
|
92
|
+
find_entry(name, @parsed_layouts, @file_handler.layouts, lang: lang)
|
|
67
93
|
end
|
|
68
94
|
|
|
69
|
-
def include name
|
|
70
|
-
find_entry(name, @parsed_includes, @file_handler.includes)
|
|
95
|
+
def include name, lang: nil
|
|
96
|
+
find_entry(name, @parsed_includes, @file_handler.includes, lang: lang)
|
|
71
97
|
end
|
|
72
98
|
|
|
73
99
|
def entry name
|
|
@@ -90,7 +116,7 @@ module Isola
|
|
|
90
116
|
|
|
91
117
|
def render_to_dest entry
|
|
92
118
|
if entry.instance_of? Source
|
|
93
|
-
rendered, path = Context.new(entry, self).render
|
|
119
|
+
rendered, path = Context.new(entry, self, languages: languages).render
|
|
94
120
|
dest_path = File.join(dest_dir, path)
|
|
95
121
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
96
122
|
File.write(dest_path, rendered)
|
|
@@ -117,19 +143,36 @@ module Isola
|
|
|
117
143
|
@parsed_entries = {}
|
|
118
144
|
end
|
|
119
145
|
|
|
120
|
-
def find_entry(name, cache, store)
|
|
121
|
-
|
|
146
|
+
def find_entry(name, cache, store, lang: nil)
|
|
147
|
+
resolved = resolve_localized(name, store, lang)
|
|
148
|
+
lang = @lang_router.language_for(resolved)
|
|
149
|
+
cache[resolved] ||=
|
|
122
150
|
begin
|
|
123
|
-
p = store[
|
|
151
|
+
p = store[resolved]
|
|
124
152
|
return nil unless p
|
|
125
153
|
if ext_to_process_with_tilt?(File.extname(p))
|
|
126
|
-
|
|
154
|
+
translations = translations_for(resolved, store)
|
|
155
|
+
Source.new(p, read_in_site(p), lang: lang, translations: translations)
|
|
127
156
|
else
|
|
128
157
|
StaticFile.new(p)
|
|
129
158
|
end
|
|
130
159
|
end
|
|
131
160
|
end
|
|
132
161
|
|
|
162
|
+
def resolve_localized(name, store, lang)
|
|
163
|
+
return name unless lang && @lang_router.language_for(name) != lang
|
|
164
|
+
localized = @lang_router.localized_path(name, lang)
|
|
165
|
+
store[localized] ? localized : name
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def translations_for(path, store)
|
|
169
|
+
@lang_router.candidate_paths(path).select do |_, candidate|
|
|
170
|
+
store.key?(candidate)
|
|
171
|
+
end.transform_values do |candidate|
|
|
172
|
+
url_path_for(candidate)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
133
176
|
def read_in_site(p)
|
|
134
177
|
File.read(File.join(config[:root_dir], p))
|
|
135
178
|
end
|
data/lib/isola/source.rb
CHANGED
|
@@ -3,13 +3,15 @@ require "yaml"
|
|
|
3
3
|
module Isola
|
|
4
4
|
class Source
|
|
5
5
|
attr_reader :filepath, :meta, :content
|
|
6
|
-
def initialize filepath, text
|
|
6
|
+
def initialize filepath, text, lang:, translations: {}
|
|
7
7
|
@filepath = filepath
|
|
8
8
|
@meta, @content = if (m = text.match(/\A---\s*\n(.+?)^---\s*\n(.*)\z/m))
|
|
9
|
-
[YAML.safe_load(m[1], symbolize_names: true), m[2]]
|
|
9
|
+
[YAML.safe_load(m[1], symbolize_names: true) || {}, m[2]]
|
|
10
10
|
else
|
|
11
11
|
[{}, text]
|
|
12
12
|
end
|
|
13
|
+
@meta[:lang] = lang
|
|
14
|
+
@meta[:translations] = translations
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def [] key
|
data/lib/isola/version.rb
CHANGED
data/lib/isola.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: isola
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Satoshi Kojima
|
|
@@ -87,6 +87,7 @@ executables:
|
|
|
87
87
|
extensions: []
|
|
88
88
|
extra_rdoc_files: []
|
|
89
89
|
files:
|
|
90
|
+
- ".rubocop.yml"
|
|
90
91
|
- LICENSE
|
|
91
92
|
- README.md
|
|
92
93
|
- Rakefile
|
|
@@ -97,6 +98,7 @@ files:
|
|
|
97
98
|
- lib/isola/context.rb
|
|
98
99
|
- lib/isola/dev_server.rb
|
|
99
100
|
- lib/isola/file_handler.rb
|
|
101
|
+
- lib/isola/language_path_router.rb
|
|
100
102
|
- lib/isola/site.rb
|
|
101
103
|
- lib/isola/source.rb
|
|
102
104
|
- lib/isola/static_file.rb
|
|
@@ -121,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
121
123
|
- !ruby/object:Gem::Version
|
|
122
124
|
version: '0'
|
|
123
125
|
requirements: []
|
|
124
|
-
rubygems_version: 4.0.
|
|
126
|
+
rubygems_version: 4.0.3
|
|
125
127
|
specification_version: 4
|
|
126
128
|
summary: very simple static site generator using ERB
|
|
127
129
|
test_files: []
|