Skip to content

Commit f35bf87

Browse files
karnkaulMes0903CDDing
authored
Customize mdbook for translations, add KO (#23)
* Preliminary multilingual support (#10) * Preliminary multilingual support mdBook does not natively support multilingual documentation. A common approach is to use a third-party library like mdbook-i18n-helper, which relies on a Gettext-based workflow to enable multilingual support. However, I believe such a complex system is unnecessary for our case. Therefore, this commit follows the approach suggested in the official mdBook documentation: overriding the 'index.hbs' template to add a button that redirects users to the corresponding documentation in another language, thus achieving basic multilingual support. This commit introduces the following three files under the theme directory: - 'index.hbs': Adds a redirect button for switching languages. - 'lang-toggle.css': Defines the styling for the button and the dropdown menu. - 'lang-toggle.js': Implements the main logic behind the button's behavior. As a result, two separate books need to be built. However, since mdBook’s 'book.toml' does not support multiple '[book]'' sections in a single configuration. This commit introduces a CMake script to build different language versions of the book and place them in their respective directories. It can be tested by command 'cmake -P build.cmake'. Due to mdBook's behavior, the 'src' field in 'book.toml' determines the root URL. Therefore, if the theme directory is placed above the src directory, mdBook will copy it relative to the 'src' path during the build, which will cause the theme folder to become unreachable. Since English is the primary language of this documentation, the default behavior supports running 'mdbook' command directly from the guide directory, such as with 'mdbook serve'. However, this will only generate the English version of the documentation, other languages will not be built. To build other language versions using the mdbook command directly, it must manually adjust their respective book.toml files. All translations are placed under 'guide/translations'. To add a new language, it need to follow the same folder structure, update the CMake script, and register the new language in 'theme/index.hbs'. [1]: https://github.com/google/mdbook-i18n-helpers * Stick to 'snake_case' for filenames * Stick to 'snake_case' for file reference * Add CI workflow to build guide books (#13) * Add build book job to CI * Test book build breakage * `execute_process()`: treat errors as fatal * Revert deliberate breakage * Replace Karn Kaul with Karnage (#14) * ko-translate (#20) * ko-translate getting_started * ko-translate initialization.md * ko-translate rendering * ko-translate ImGui * ko-translate shader_objects * ko-translate memory * ko-translate descriptorSets * revise and summary * fix --------- Co-authored-by: Karn Kaul <[email protected]> * Remove zh-TW --------- Co-authored-by: Mes <[email protected]> Co-authored-by: MyungKun Chang <[email protected]>
1 parent c65e360 commit f35bf87

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4210
-4
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: build book
2525
run: |
2626
cd guide
27-
mdbook build
27+
cmake -P build.cmake || exit 1
2828
ls book
2929
- name: setup pages
3030
uses: actions/configure-pages@v4

.github/workflows/guide.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ jobs:
1919
- name: build
2020
run: |
2121
cd guide
22-
mdbook build || exit 1
22+
cmake -P build.cmake || exit 1
2323
ls book

guide/book.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
[book]
2-
authors = ["Karn Kaul"]
2+
authors = ["Karnage"]
33
language = "en"
4-
multilingual = false
54
src = "src"
65
title = "Learn Vulkan"
6+
7+
[output.html]
8+
theme = "theme"
9+
additional-js = ["theme/lang_toggle.js"]
10+
additional-css = ["theme/lang_toggle.css"]

guide/build.cmake

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Build the target languages
2+
function(BuildBook LANGUAGE SOURCE_DIR TARGET_DIR)
3+
set(LANGUAGE "${LANGUAGE}")
4+
5+
if(NOT EXISTS "${SOURCE_DIR}/src/SUMMARY.md")
6+
message(WARNING "Skipping '${LANGUAGE}' – SUMMARY.md not found at ${SOURCE_DIR}")
7+
return()
8+
endif()
9+
10+
if(NOT EXISTS "${SOURCE_DIR}/book.toml")
11+
message(WARNING "Skipping '${LANGUAGE}' – book.toml not found at ${SOURCE_DIR}")
12+
return()
13+
endif()
14+
15+
message(STATUS "Building book for language: ${LANGUAGE}")
16+
execute_process(
17+
COMMAND mdbook build -d ${TARGET_DIR}
18+
WORKING_DIRECTORY ${SOURCE_DIR}
19+
COMMAND_ERROR_IS_FATAL ANY
20+
)
21+
endfunction()
22+
23+
# Copy the theme folder
24+
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/theme" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/translations")
25+
26+
BuildBook("en" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/book")
27+
BuildBook("ko-KR" "${CMAKE_CURRENT_SOURCE_DIR}/translations/ko-KR" "${CMAKE_CURRENT_SOURCE_DIR}/book/ko-KR")

guide/theme/index.hbs

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
<!DOCTYPE HTML>
2+
<html lang="{{ language }}" class="{{ default_theme }} sidebar-visible" dir="{{ text_direction }}">
3+
<head>
4+
<!-- Book generated using mdBook -->
5+
<meta charset="UTF-8">
6+
<title>{{ title }}</title>
7+
{{#if is_print }}
8+
<meta name="robots" content="noindex">
9+
{{/if}}
10+
{{#if base_url}}
11+
<base href="{{ base_url }}">
12+
{{/if}}
13+
14+
15+
<!-- Custom HTML head -->
16+
{{> head}}
17+
18+
<meta name="description" content="{{ description }}">
19+
<meta name="viewport" content="width=device-width, initial-scale=1">
20+
<meta name="theme-color" content="#ffffff">
21+
22+
{{#if favicon_svg}}
23+
<link rel="icon" href="{{ resource "favicon.svg" }}">
24+
{{/if}}
25+
{{#if favicon_png}}
26+
<link rel="shortcut icon" href="{{ resource "favicon.png" }}">
27+
{{/if}}
28+
<link rel="stylesheet" href="{{ resource "css/variables.css" }}">
29+
<link rel="stylesheet" href="{{ resource "css/general.css" }}">
30+
<link rel="stylesheet" href="{{ resource "css/chrome.css" }}">
31+
{{#if print_enable}}
32+
<link rel="stylesheet" href="{{ resource "css/print.css" }}" media="print">
33+
{{/if}}
34+
35+
<!-- Fonts -->
36+
<link rel="stylesheet" href="{{ resource "FontAwesome/css/font-awesome.css" }}">
37+
{{#if copy_fonts}}
38+
<link rel="stylesheet" href="{{ resource "fonts/fonts.css" }}">
39+
{{/if}}
40+
41+
<!-- Highlight.js Stylesheets -->
42+
<link rel="stylesheet" id="highlight-css" href="{{ resource "highlight.css" }}">
43+
<link rel="stylesheet" id="tomorrow-night-css" href="{{ resource "tomorrow-night.css" }}">
44+
<link rel="stylesheet" id="ayu-highlight-css" href="{{ resource "ayu-highlight.css" }}">
45+
46+
<!-- Custom theme stylesheets -->
47+
{{#each additional_css}}
48+
<link rel="stylesheet" href="{{ resource this }}">
49+
{{/each}}
50+
51+
{{#if mathjax_support}}
52+
<!-- MathJax -->
53+
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
54+
{{/if}}
55+
56+
<!-- Provide site root to javascript -->
57+
<script>
58+
const path_to_root = "{{ path_to_root }}";
59+
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
60+
</script>
61+
<!-- Start loading toc.js asap -->
62+
<script src="{{ resource "toc.js" }}"></script>
63+
</head>
64+
<body>
65+
<div id="body-container">
66+
<!-- Work around some values being stored in localStorage wrapped in quotes -->
67+
<script>
68+
try {
69+
let theme = localStorage.getItem('mdbook-theme');
70+
let sidebar = localStorage.getItem('mdbook-sidebar');
71+
72+
if (theme.startsWith('"') && theme.endsWith('"')) {
73+
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
74+
}
75+
76+
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
77+
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
78+
}
79+
} catch (e) { }
80+
</script>
81+
82+
<!-- Set the theme before any content is loaded, prevents flash -->
83+
<script>
84+
let theme;
85+
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
86+
if (theme === null || theme === undefined) { theme = default_theme; }
87+
const html = document.documentElement;
88+
html.classList.remove('{{ default_theme }}')
89+
html.classList.add(theme);
90+
html.classList.add("js");
91+
</script>
92+
93+
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
94+
95+
<!-- Hide / unhide sidebar before it is displayed -->
96+
<script>
97+
let sidebar = null;
98+
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
99+
if (document.body.clientWidth >= 1080) {
100+
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
101+
sidebar = sidebar || 'visible';
102+
} else {
103+
sidebar = 'hidden';
104+
}
105+
sidebar_toggle.checked = sidebar === 'visible';
106+
html.classList.remove('sidebar-visible');
107+
html.classList.add("sidebar-" + sidebar);
108+
</script>
109+
110+
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
111+
<!-- populated by js -->
112+
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
113+
<noscript>
114+
<iframe class="sidebar-iframe-outer" src="{{ path_to_root }}toc.html"></iframe>
115+
</noscript>
116+
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
117+
<div class="sidebar-resize-indicator"></div>
118+
</div>
119+
</nav>
120+
121+
<div id="page-wrapper" class="page-wrapper">
122+
123+
<div class="page">
124+
{{> header}}
125+
<div id="menu-bar-hover-placeholder"></div>
126+
<div id="menu-bar" class="menu-bar sticky">
127+
<div class="left-buttons">
128+
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
129+
<i class="fa fa-bars"></i>
130+
</label>
131+
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
132+
<i class="fa fa-paint-brush"></i>
133+
</button>
134+
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
135+
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
136+
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
137+
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
138+
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
139+
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
140+
</ul>
141+
<button id="lang-toggle" class="icon-button" type="button" title="Change language" aria-label="Change language" aria-haspopup="true" aria-expanded="false" aria-controls="lang-list">
142+
<i class="fa fa-language"></i>
143+
</button>
144+
<ul id="lang-list" class="theme-popup" aria-label="Languages" role="menu">
145+
<li role="none"><button role="menuitem" class="theme" data-lang="en">English</button></li>
146+
<li role="none"><button role="menuitem" class="theme" data-lang="ko-KR">한글</button></li>
147+
</ul>
148+
{{#if search_enabled}}
149+
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
150+
<i class="fa fa-search"></i>
151+
</button>
152+
{{/if}}
153+
</div>
154+
155+
<h1 class="menu-title">{{ book_title }}</h1>
156+
157+
<div class="right-buttons">
158+
{{#if print_enable}}
159+
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
160+
<i id="print-button" class="fa fa-print"></i>
161+
</a>
162+
{{/if}}
163+
{{#if git_repository_url}}
164+
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
165+
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
166+
</a>
167+
{{/if}}
168+
{{#if git_repository_edit_url}}
169+
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
170+
<i id="git-edit-button" class="fa fa-edit"></i>
171+
</a>
172+
{{/if}}
173+
174+
</div>
175+
</div>
176+
177+
{{#if search_enabled}}
178+
<div id="search-wrapper" class="hidden">
179+
<form id="searchbar-outer" class="searchbar-outer">
180+
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
181+
</form>
182+
<div id="searchresults-outer" class="searchresults-outer hidden">
183+
<div id="searchresults-header" class="searchresults-header"></div>
184+
<ul id="searchresults">
185+
</ul>
186+
</div>
187+
</div>
188+
{{/if}}
189+
190+
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
191+
<script>
192+
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
193+
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
194+
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
195+
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
196+
});
197+
</script>
198+
199+
<div id="content" class="content">
200+
<main>
201+
{{{ content }}}
202+
</main>
203+
204+
<nav class="nav-wrapper" aria-label="Page navigation">
205+
<!-- Mobile navigation buttons -->
206+
{{#previous}}
207+
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
208+
<i class="fa fa-angle-left"></i>
209+
</a>
210+
{{/previous}}
211+
212+
{{#next}}
213+
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
214+
<i class="fa fa-angle-right"></i>
215+
</a>
216+
{{/next}}
217+
218+
<div style="clear: both"></div>
219+
</nav>
220+
</div>
221+
</div>
222+
223+
<nav class="nav-wide-wrapper" aria-label="Page navigation">
224+
{{#previous}}
225+
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
226+
<i class="fa fa-angle-left"></i>
227+
</a>
228+
{{/previous}}
229+
230+
{{#next}}
231+
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
232+
<i class="fa fa-angle-right"></i>
233+
</a>
234+
{{/next}}
235+
</nav>
236+
237+
</div>
238+
239+
{{#if live_reload_endpoint}}
240+
<!-- Livereload script (if served using the cli tool) -->
241+
<script>
242+
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
243+
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
244+
const socket = new WebSocket(wsAddress);
245+
socket.onmessage = function (event) {
246+
if (event.data === "reload") {
247+
socket.close();
248+
location.reload();
249+
}
250+
};
251+
252+
window.onbeforeunload = function() {
253+
socket.close();
254+
}
255+
</script>
256+
{{/if}}
257+
258+
{{#if google_analytics}}
259+
<!-- Google Analytics Tag -->
260+
<script>
261+
const localAddrs = ["localhost", "127.0.0.1", ""];
262+
263+
// make sure we don't activate google analytics if the developer is
264+
// inspecting the book locally...
265+
if (localAddrs.indexOf(document.location.hostname) === -1) {
266+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
267+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
268+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
269+
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
270+
271+
ga('create', '{{google_analytics}}', 'auto');
272+
ga('send', 'pageview');
273+
}
274+
</script>
275+
{{/if}}
276+
277+
{{#if playground_line_numbers}}
278+
<script>
279+
window.playground_line_numbers = true;
280+
</script>
281+
{{/if}}
282+
283+
{{#if playground_copyable}}
284+
<script>
285+
window.playground_copyable = true;
286+
</script>
287+
{{/if}}
288+
289+
{{#if playground_js}}
290+
<script src="{{ resource "ace.js" }}"></script>
291+
<script src="{{ resource "editor.js" }}"></script>
292+
<script src="{{ resource "mode-rust.js" }}"></script>
293+
<script src="{{ resource "theme-dawn.js" }}"></script>
294+
<script src="{{ resource "theme-tomorrow_night.js" }}"></script>
295+
{{/if}}
296+
297+
{{#if search_js}}
298+
<script src="{{ resource "elasticlunr.min.js" }}"></script>
299+
<script src="{{ resource "mark.min.js" }}"></script>
300+
<script src="{{ resource "searcher.js" }}"></script>
301+
{{/if}}
302+
303+
<script src="{{ resource "clipboard.min.js" }}"></script>
304+
<script src="{{ resource "highlight.js" }}"></script>
305+
<script src="{{ resource "book.js" }}"></script>
306+
307+
<!-- Custom JS scripts -->
308+
{{#each additional_js}}
309+
<script src="{{ resource this}}"></script>
310+
{{/each}}
311+
312+
{{#if is_print}}
313+
{{#if mathjax_support}}
314+
<script>
315+
window.addEventListener('load', function() {
316+
MathJax.Hub.Register.StartupHook('End', function() {
317+
window.setTimeout(window.print, 100);
318+
});
319+
});
320+
</script>
321+
{{else}}
322+
<script>
323+
window.addEventListener('load', function() {
324+
window.setTimeout(window.print, 100);
325+
});
326+
</script>
327+
{{/if}}
328+
{{/if}}
329+
330+
</div>
331+
</body>
332+
</html>

0 commit comments

Comments
 (0)