Skip to content

Commit c3ea47d

Browse files
piti6claude
andcommitted
feat: add JP/EN language toggle and multi-language web build
- Add language toggle (日本語 / English) in sidebar - Build both JP and EN web outputs into webroot/ja/ and webroot/en/ - Root index.html redirects to Japanese version - Search index generated per language - Add build-web.sh to orchestrate multi-language build - Update npm web script and GitHub Actions workflow accordingly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ebac23b commit c3ea47d

6 files changed

Lines changed: 97 additions & 8 deletions

File tree

.github/workflows/deploy-web.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,8 @@ jobs:
3939
- name: Install gem dependencies
4040
run: bundle install
4141

42-
- name: Build web pages
43-
run: REVIEW_CONFIG_FILE=config-epub-jp.yml npx grunt web
44-
45-
- name: Build search index
46-
run: node build-search-index.js
42+
- name: Build web pages (JP + EN)
43+
run: npm run web
4744

4845
- name: Setup Pages
4946
uses: actions/configure-pages@v4

articles/layouts/layout-web.html.erb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
<% end %>
2929
<h1 class="side-title"><%=h @book.config.name_of("booktitle") %></h1>
3030
</div>
31+
<div class="lang-toggle" id="lang-toggle">
32+
<a href="#" class="lang-btn" data-lang="ja">日本語</a>
33+
<a href="#" class="lang-btn" data-lang="en">English</a>
34+
</div>
3135
<div class="search-box">
3236
<input type="text" id="search-input" placeholder="Search..." autocomplete="off" />
3337
<div id="search-results" class="search-results"></div>
@@ -200,6 +204,27 @@
200204
return snippet;
201205
}
202206
})();
207+
208+
// --- Language Toggle ---
209+
(function() {
210+
var path = location.pathname;
211+
var langMatch = path.match(/\/(ja|en)\//);
212+
var currentLang = langMatch ? langMatch[1] : 'ja';
213+
214+
// Highlight active language
215+
document.querySelectorAll('.lang-btn').forEach(function(btn) {
216+
var lang = btn.getAttribute('data-lang');
217+
if (lang === currentLang) {
218+
btn.classList.add('lang-active');
219+
}
220+
btn.addEventListener('click', function(e) {
221+
e.preventDefault();
222+
if (lang === currentLang) return;
223+
var newPath = path.replace('/' + currentLang + '/', '/' + lang + '/');
224+
location.href = newPath + location.hash;
225+
});
226+
});
227+
})();
203228
</script>
204229
</body>
205230
</html>

articles/style-web.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,35 @@ nav.side-content {
6060
nav.side-content .review-signature a {
6161
color: #6b7280; }
6262

63+
/* --- Language Toggle --- */
64+
.lang-toggle {
65+
display: flex;
66+
gap: 0;
67+
margin-bottom: 12px;
68+
border: 1px solid #d1d5db;
69+
border-radius: 6px;
70+
overflow: hidden; }
71+
72+
.lang-btn {
73+
flex: 1;
74+
display: block;
75+
padding: 6px 0;
76+
text-align: center;
77+
font-size: 0.78em;
78+
font-weight: 500;
79+
color: #6b7280;
80+
text-decoration: none;
81+
background: #fff;
82+
transition: background 0.15s, color 0.15s; }
83+
.lang-btn:hover {
84+
background: #f3f4f6;
85+
text-decoration: none; }
86+
.lang-btn.lang-active {
87+
background: #1f2937;
88+
color: #fff; }
89+
.lang-btn + .lang-btn {
90+
border-left: 1px solid #d1d5db; }
91+
6392
/* --- Search --- */
6493
.search-box {
6594
position: relative;

build-search-index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use strict";
22

33
// Generates search-index.json from webroot HTML files.
4-
// Run after review-webmaker: node build-search-index.js
4+
// Usage: node build-search-index.js [directory]
55

66
const fs = require("fs");
77
const path = require("path");
88

9-
const webroot = path.join(__dirname, "articles", "webroot");
9+
const webroot = process.argv[2] || path.join(__dirname, "articles", "webroot");
1010
const outFile = path.join(webroot, "search-index.json");
1111

1212
const htmlFiles = fs.readdirSync(webroot).filter(f => f.endsWith(".html") && f !== "index.html" && f !== "titlepage.html");

build-web.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
set -e
3+
4+
WEBROOT="articles/webroot"
5+
OUTDIR="articles/webroot-dist"
6+
7+
rm -rf "$OUTDIR"
8+
mkdir -p "$OUTDIR/ja" "$OUTDIR/en"
9+
10+
# Build Japanese
11+
echo "=== Building Japanese ==="
12+
REVIEW_CONFIG_FILE=config-epub-jp.yml npx grunt web
13+
cp -r "$WEBROOT/"* "$OUTDIR/ja/"
14+
15+
# Build English
16+
echo "=== Building English ==="
17+
REVIEW_CONFIG_FILE=config-epub-en.yml npx grunt web
18+
cp -r "$WEBROOT/"* "$OUTDIR/en/"
19+
20+
# Generate search indices
21+
echo "=== Building search indices ==="
22+
node build-search-index.js "$OUTDIR/ja"
23+
node build-search-index.js "$OUTDIR/en"
24+
25+
# Create root redirect
26+
cat > "$OUTDIR/index.html" << 'REDIRECT'
27+
<!DOCTYPE html>
28+
<html>
29+
<head><meta http-equiv="refresh" content="0; url=ja/index.html" /></head>
30+
<body><a href="ja/index.html">日本語版へ / Go to Japanese version</a></body>
31+
</html>
32+
REDIRECT
33+
34+
# Replace webroot with final output
35+
rm -rf "$WEBROOT"
36+
mv "$OUTDIR" "$WEBROOT"
37+
38+
echo "=== Done: $WEBROOT/ja/ and $WEBROOT/en/ ==="

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"html": "grunt html",
2626
"text": "grunt text",
2727
"epub": "grunt epub",
28-
"web": "grunt web && node build-search-index.js",
28+
"web": "bash build-web.sh",
2929
"idgxml": "grunt idgxmlmaker",
3030
"vivliostyle": "grunt vivliostyle",
3131
"test": "npm run html"

0 commit comments

Comments
 (0)