diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index 2d7d6cad..73885e67 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -43,6 +43,11 @@ jobs:
MDBOOK_OUTPUT__LINKCHECK__OPTIONAL: "false"
run: |
mdbook build
+ for f in po/*.po; do
+ lang=$(basename -s .po "$f")
+ MDBOOK_BOOK__LANGUAGE="$lang" mdbook build -d book/"$lang"
+ mv book/"$lang"/custom book/custom/"$lang"
+ done
- name: Store final build
uses: actions/upload-artifact@v2
diff --git a/.gitignore b/.gitignore
index 42654af5..7265a402 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/book/
/target/
.DS_Store
+/po/messages.pot
diff --git a/Cargo.lock b/Cargo.lock
index 5d982a3f..0dd9a4e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -65,6 +65,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -98,6 +104,18 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+
[[package]]
name = "cc"
version = "1.0.79"
@@ -139,9 +157,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.1.6"
+version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
+checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
dependencies = [
"bitflags",
"clap_lex",
@@ -154,11 +172,11 @@ dependencies = [
[[package]]
name = "clap_complete"
-version = "4.1.3"
+version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0012995dc3a54314f4710f5631d74767e73c534b8757221708303e48eef7a19b"
+checksum = "501ff0a401473ea1d4c3b125ff95506b62c5bc5768d818634195fbb7c4ad5ff4"
dependencies = [
- "clap 4.1.6",
+ "clap 4.1.8",
]
[[package]]
@@ -195,6 +213,25 @@ dependencies = [
"libc",
]
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -314,6 +351,24 @@ dependencies = [
"instant",
]
+[[package]]
+name = "filetime"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
[[package]]
name = "form_urlencoded"
version = "1.1.0"
@@ -323,6 +378,15 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "futf"
version = "0.1.5"
@@ -333,6 +397,60 @@ dependencies = [
"new_debug_unreachable",
]
+[[package]]
+name = "futures-channel"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
+
+[[package]]
+name = "futures-task"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
+
+[[package]]
+name = "futures-util"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
+dependencies = [
+ "futures-core",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
[[package]]
name = "generic-array"
version = "0.14.6"
@@ -363,6 +481,40 @@ dependencies = [
"wasi",
]
+[[package]]
+name = "gitignore"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78aa90e4620c1498ac434c06ba6e521b525794bbdacf085d490cc794b4a2f9a4"
+dependencies = [
+ "glob",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "h2"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
[[package]]
name = "handlebars"
version = "4.3.6"
@@ -377,6 +529,37 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "headers"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes",
+ "headers-core",
+ "http",
+ "httpdate",
+ "mime",
+ "sha1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
[[package]]
name = "hermit-abi"
version = "0.1.19"
@@ -386,6 +569,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "hermit-abi"
version = "0.3.1"
@@ -406,12 +598,84 @@ dependencies = [
"syn",
]
+[[package]]
+name = "http"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+[[package]]
+name = "hyper"
+version = "0.14.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "i18n-helpers"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "mdbook",
+ "once_cell",
+ "polib",
+ "regex",
+ "semver",
+ "serde_json",
+ "toml",
+]
+
[[package]]
name = "iana-time-zone"
version = "0.1.53"
@@ -446,6 +710,36 @@ dependencies = [
"unicode-normalization",
]
+[[package]]
+name = "indexmap"
+version = "1.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "instant"
version = "0.1.12"
@@ -462,7 +756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
dependencies = [
"libc",
- "windows-sys",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -474,7 +768,7 @@ dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
- "windows-sys",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -492,6 +786,26 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "kqueue"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -573,13 +887,17 @@ dependencies = [
"ammonia",
"anyhow",
"chrono",
- "clap 4.1.6",
+ "clap 4.1.8",
"clap_complete",
"elasticlunr-rs",
"env_logger",
+ "futures-util",
+ "gitignore",
"handlebars",
"log",
"memchr",
+ "notify",
+ "notify-debouncer-mini",
"once_cell",
"opener",
"pulldown-cmark 0.9.2",
@@ -588,8 +906,10 @@ dependencies = [
"serde_json",
"shlex",
"tempfile",
+ "tokio",
"toml",
"topological-sort",
+ "warp",
]
[[package]]
@@ -598,12 +918,68 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.45.0",
+]
+
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+[[package]]
+name = "notify"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9"
+dependencies = [
+ "bitflags",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "mio",
+ "walkdir",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "notify-debouncer-mini"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e23e9fa24f094b143c1eb61f90ac6457de87be6987bc70746e0179f7dbc9007b"
+dependencies = [
+ "crossbeam-channel",
+ "notify",
+]
+
[[package]]
name = "num-integer"
version = "0.1.45"
@@ -623,6 +999,16 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi 0.2.6",
+ "libc",
+]
+
[[package]]
name = "once_cell"
version = "1.17.1"
@@ -665,7 +1051,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
- "windows-sys",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -756,6 +1142,44 @@ dependencies = [
"siphasher",
]
+[[package]]
+name = "pin-project"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "polib"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17009af1604eef4137497a743594fbe8f37e52f004cb5d8f7cf5130dc74a5644"
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -895,15 +1319,6 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
-[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi",
-]
-
[[package]]
name = "renderer"
version = "0.1.0"
@@ -926,7 +1341,16 @@ dependencies = [
"io-lifetimes",
"libc",
"linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9"
+dependencies = [
+ "base64",
]
[[package]]
@@ -935,6 +1359,21 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
[[package]]
name = "scopeguard"
version = "1.1.0"
@@ -947,6 +1386,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
+[[package]]
+name = "semver"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
+
[[package]]
name = "serde"
version = "1.0.152"
@@ -978,6 +1423,40 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "sha2"
version = "0.10.6"
@@ -1001,12 +1480,31 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
[[package]]
name = "string_cache"
version = "0.8.4"
@@ -1058,16 +1556,15 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.3.0"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
dependencies = [
"cfg-if",
"fastrand",
- "libc",
"redox_syscall",
- "remove_dir_all",
- "winapi",
+ "rustix",
+ "windows-sys 0.42.0",
]
[[package]]
@@ -1097,7 +1594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a"
dependencies = [
"rustix",
- "windows-sys",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -1144,6 +1641,72 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+[[package]]
+name = "tokio"
+version = "1.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181"
+dependencies = [
+ "futures-util",
+ "log",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
[[package]]
name = "toml"
version = "0.5.11"
@@ -1159,6 +1722,58 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d"
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "tungstenite"
+version = "0.17.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
+dependencies = [
+ "base64",
+ "byteorder",
+ "bytes",
+ "http",
+ "httparse",
+ "log",
+ "rand",
+ "sha-1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
[[package]]
name = "typenum"
version = "1.16.0"
@@ -1236,6 +1851,57 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "warp"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "headers",
+ "http",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project",
+ "rustls-pemfile",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-tungstenite",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -1327,6 +1993,21 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
[[package]]
name = "windows-sys"
version = "0.45.0"
diff --git a/Cargo.toml b/Cargo.toml
index 3bd4a80a..576cc796 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,2 @@
[workspace]
-members = [ "preproc", "renderer" ]
+members = [ "preproc", "renderer", "i18n-helpers" ]
diff --git a/README.md b/README.md
index c54a6c30..77aea263 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
Re-doing [GB ASM Tutorial](https://github.com/ISSOtm/gb-asm-tutorial-old), and this time, until the end.
+## Translating
+
+To add a translation or contribute on an existing one, please see [TRANSLATING](TRANSLATING.md).
+
## Contributing
Contributing is really easy, fork this repo and edit the files in the **src** directory. Then, you can send your PR.
@@ -10,6 +14,9 @@ To deploy gb-asm-tutorial locally:
1. Install [Rust](https://www.rust-lang.org/tools/install) and [mdBook](https://github.com/rust-lang/mdBook#readme).
mdBook powers the book itself, Rust is used for some custom plugins.
+ ```
+ $ cargo install mdbook
+ ```
2. Within a terminal pointed at the directory `book.toml` is in, run mdBook (`mdbook build` / `mdbook watch` / `mdbook serve`).
3. The HTML files are in `book/custom/`.
@@ -23,4 +30,6 @@ Different parts of gb-asm-tutorial are subject to different licenses:
- All the code contained within the tutorial itself is licensed under CC0. *To the extent possible under law, all copyright and related or neighboring rights to code presented within GB ASM Tutorial have been waived. This work is published from France.*
- The contents (prose, images, etc.) of this tutorial are licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
-- Code used to display and format the site is licensed under the [MIT License](https://github.com/ISSOtm/gb-asm-tutorial/blob/master/LICENSE) unless otherwise specified.
+- Code used to display and format the site is licensed under the [MIT License](https://github.com/gbdev/gb-asm-tutorial/blob/master/LICENSE) unless otherwise specified.
+- The code related to the i18n support is originally from Google's [Comprehensive Rust](https://github.com/google/comprehensive-rust) and is released under the [Apache License 2.0](https://github.com/gbdev/gb-asm-tutorial/blob/master/i18n-helpers/LICENSE).
+
diff --git a/TRANSLATING.md b/TRANSLATING.md
new file mode 100644
index 00000000..54c476a4
--- /dev/null
+++ b/TRANSLATING.md
@@ -0,0 +1,120 @@
+# Translating the GB ASM Tutorial
+
+We would love to have your help with translating the tutorial into other
+languages! We use the [Gettext] system for translations. This means that you
+don't modify the Markdown files directly: instead you modify `.po` files in a
+`po/` directory. The `.po` files are small text-based translation databases.
+
+> **Tip:** You should not edit the `.po` files by hand. Instead use a PO
+> editor, such as [Poedit](https://poedit.net/). There are also several online
+> editors available. This will ensure that the file is encoded correctly.
+
+There is a `.po` file for each language. They are named after the [ISO 639]
+language codes: Danish would go into `po/da.po`, Korean would go into
+`po/ko.po`, etc. The `.po` files contain all the English text plus the
+translations. They are initialized from a `messages.pot` file (a PO template)
+which contains only the English text.
+
+We will show how to update and manipulate the `.po` and `.pot` files using the
+GNU Gettext utilities below.
+
+[Gettext]: https://www.gnu.org/software/gettext/manual/html_node/index.html
+[ISO 639]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+
+## I18n Helpers
+
+We use two helpers for the translations:
+
+* `mdbook-xgettext`: This program extracts the English text. It is an mdbook
+ renderer.
+* `mdbook-gettext`: This program translates the book into a target language. It
+ is an mdbook preprocessor.
+
+Install both helpers with the following command from the root of the course:
+
+```shell
+$ cargo install --path i18n-helpers
+```
+
+## Creating and Updating Translations
+
+First, you need to know how to update the `.pot` and `.po` files.
+
+As a general rule, you should never touch the auto-generated `po/messages.pot`
+file. You should also not edit the `msgid` entries in a `po/xx.po` file. If you
+find mistakes, you need to update the original English text instead. The fixes
+to the English text will flow into the `.po` files the next time the translators
+update them.
+
+### Generating the PO Template
+
+To extract the original English text and generate a `messages.pot` file, you run
+`mdbook` with a special renderer:
+
+```shell
+$ MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' \
+ mdbook build -d po
+```
+
+You will find the generated POT file as `po/messages.pot`.
+
+### Initialize a New Translation
+
+To start a new translation, first generate the `po/messages.pot` file. Then use
+`msginit` to create a `xx.po` file for the fictional `xx` language:
+
+```shell
+$ msginit -i po/messages.pot -l xx -o po/xx.po
+```
+
+You can also simply copy `po/messages.pot` to `po/xx.po`. Then update the file
+header (the first entry with `msgid ""`) to the correct language.
+
+### Adding a new translation to the menu
+
+To add a new translation in the language menu in the navbar, add an entry to
+the `language-list` list in [`theme/index.hbs`](https://github.com/gbdev/gb-asm-tutorial/blob/master/theme/index.hbs#L145).
+
+### Updating an Existing Translation
+
+As the English text changes, translations gradually become outdated. To update
+the `po/xx.po` file with new messages, first extract the English text into a
+`po/messages.pot` template file. Then run
+
+```shell
+$ msgmerge --update po/xx.po po/messages.pot
+```
+
+Unchanged messages will stay intact, deleted messages are marked as old, and
+updated messages are marked "fuzzy". A fuzzy entry will reuse the previous
+translation: you should then go over it and update it as necessary before you
+remove the fuzzy marker.
+
+## Using Translations
+
+This will show you how to use the translations to generate localized HTML
+output.
+
+Note: `mdbook` will use original untranslated entries for all entries marked as
+"fuzzy" (visible as "Needs work" in Poedit).
+
+### Building a Translation
+
+To use the `po/xx.po` file for your output, run the following command:
+
+```shell
+$ MDBOOK_BOOK__LANGUAGE=xx mdbook build -d book/xx
+```
+
+This will update the book's language to `xx`, it will make the `mdbook-gettext`
+preprocessor become active and tell it to use the `po/xx.po` file, and finally
+it will redirect the output to `book/xx`.
+
+### Serving a Translation
+
+As usual, you can use `mdbook serve` to view your translation as you work on
+it. You use the same command as with `mdbook build` above:
+
+```shell
+$ MDBOOK_BOOK__LANGUAGE=xx mdbook serve -d book/xx
+```
diff --git a/book.toml b/book.toml
index 24110f64..25bb36a0 100644
--- a/book.toml
+++ b/book.toml
@@ -8,9 +8,14 @@ src = "src"
[build]
create-missing = true # This is kept for convenience, but CI sets it to false
use-default-preprocessors = false
+extra-watch-dirs = ["po"]
[preprocessor.links]
+[preprocessor.gettext]
+command = "cargo run -p i18n-helpers --bin mdbook-gettext --locked --release --"
+before = ["links"]
+
# Custom preprocessor for our custom markup
[preprocessor.custom]
command = "cargo run -p preproc --locked --release --"
diff --git a/i18n-helpers/Cargo.toml b/i18n-helpers/Cargo.toml
new file mode 100644
index 00000000..29118b25
--- /dev/null
+++ b/i18n-helpers/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "i18n-helpers"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+anyhow = "1.0.68"
+mdbook = "0.4.25"
+once_cell = "1.17.0"
+polib = "0.1.0"
+regex = "1.7.0"
+semver = "1.0.16"
+serde_json = "1.0.91"
+toml = "0.5.1"
diff --git a/i18n-helpers/LICENSE b/i18n-helpers/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/i18n-helpers/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/i18n-helpers/src/bin/mdbook-gettext.rs b/i18n-helpers/src/bin/mdbook-gettext.rs
new file mode 100644
index 00000000..2325d674
--- /dev/null
+++ b/i18n-helpers/src/bin/mdbook-gettext.rs
@@ -0,0 +1,248 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! `gettext` for `mdbook`
+//!
+//! This program works like `gettext`, meaning it will translate
+//! strings in your book.
+//!
+//! The translations come from GNU Gettext `xx.po` files. The PO file is
+//! is found under `po` directory based on the `book.language`.
+//! For example, `book.langauge` is set to `ko`, then `po/ko.po` is used.
+//! You can set `preprocessor.gettext.po-dir` to specify where to find PO
+//! files. If the PO file is not found, you'll get the untranslated book.
+//!
+//! See `TRANSLATIONS.md` in the repository root for more information.
+
+use anyhow::{anyhow, Context};
+use i18n_helpers::extract_paragraphs;
+use mdbook::book::Book;
+use mdbook::preprocess::{CmdPreprocessor, PreprocessorContext};
+use mdbook::BookItem;
+use polib::catalog::Catalog;
+use polib::po_file;
+use semver::{Version, VersionReq};
+use std::io;
+use std::process;
+use toml::Value;
+
+fn translate(text: &str, catalog: &Catalog) -> String {
+ let mut output = String::with_capacity(text.len());
+ let mut target_lineno = 1;
+
+ for (lineno, paragraph) in extract_paragraphs(text) {
+ // Fill in blank lines between paragraphs. This is important
+ // for code blocks where blank lines are significant.
+ while target_lineno < lineno {
+ output.push('\n');
+ target_lineno += 1;
+ }
+ // Subtract 1 because the paragraph is missing a final '\n'
+ // due to the splitting in `extract_paragraphs`.
+ target_lineno += paragraph.lines().count() - 1;
+
+ let translated = catalog
+ .find_message(paragraph)
+ .filter(|msg| !msg.flags.contains("fuzzy"))
+ .and_then(|msg| msg.get_msgstr().ok())
+ .filter(|msgstr| !msgstr.is_empty())
+ .map(|msgstr| msgstr.as_str())
+ .unwrap_or(paragraph);
+ output.push_str(translated);
+ }
+
+ let suffix = &text[text.trim_end_matches('\n').len()..];
+ output.push_str(suffix);
+ output
+}
+
+fn translate_book(ctx: &PreprocessorContext, mut book: Book) -> anyhow::Result {
+ // no-op when the target language is not set
+ if ctx.config.book.language.is_none() {
+ return Ok(book);
+ }
+
+ // the target language
+ let language = ctx.config.book.language.as_ref().unwrap();
+
+ // Find PO file for the target language
+ let cfg = ctx
+ .config
+ .get_preprocessor("gettext")
+ .ok_or_else(|| anyhow!("Could not read preprocessor.gettext configuration"))?;
+ let po_dir = cfg.get("po-dir").and_then(Value::as_str).unwrap_or("po");
+ let path = ctx.root.join(po_dir).join(format!("{language}.po"));
+
+ // no-op when PO file is missing
+ if !path.exists() {
+ return Ok(book);
+ }
+
+ let catalog = po_file::parse(&path)
+ .map_err(|err| anyhow!("{err}"))
+ .with_context(|| format!("Could not parse {:?} as PO file", path))?;
+ book.for_each_mut(|item| match item {
+ BookItem::Chapter(ch) => {
+ ch.content = translate(&ch.content, &catalog);
+ ch.name = translate(&ch.name, &catalog);
+ }
+ BookItem::Separator => {}
+ BookItem::PartTitle(title) => {
+ *title = translate(title, &catalog);
+ }
+ });
+
+ Ok(book)
+}
+
+fn preprocess() -> anyhow::Result<()> {
+ let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
+ let book_version = Version::parse(&ctx.mdbook_version)?;
+ let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
+ if !version_req.matches(&book_version) {
+ eprintln!(
+ "Warning: The gettext preprocessor was built against \
+ mdbook version {}, but we're being called from version {}",
+ mdbook::MDBOOK_VERSION,
+ ctx.mdbook_version
+ );
+ }
+
+ let translated_book = translate_book(&ctx, book)?;
+ serde_json::to_writer(io::stdout(), &translated_book)?;
+
+ Ok(())
+}
+
+fn main() -> anyhow::Result<()> {
+ if std::env::args().len() == 3 {
+ assert_eq!(std::env::args().nth(1).as_deref(), Some("supports"));
+ if let Some("xgettext") = std::env::args().nth(2).as_deref() {
+ process::exit(1)
+ } else {
+ // Signal that we support all other renderers.
+ process::exit(0);
+ }
+ }
+
+ preprocess()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use polib::message::Message;
+
+ fn create_catalog(translations: &[(&str, &str)]) -> Catalog {
+ let mut catalog = Catalog::new();
+ for (msgid, msgstr) in translations {
+ let message = Message::new_singular("", "", "", "", msgid, msgstr);
+ catalog.add_message(message);
+ }
+ catalog
+ }
+
+ #[test]
+ fn test_translate_single_line() {
+ let catalog = create_catalog(&[("foo bar", "FOO BAR")]);
+ assert_eq!(translate("foo bar", &catalog), "FOO BAR");
+ }
+
+ #[test]
+ fn test_translate_single_paragraph() {
+ let catalog = create_catalog(&[("foo bar", "FOO BAR")]);
+ assert_eq!(translate("foo bar\n", &catalog), "FOO BAR\n");
+ }
+
+ #[test]
+ fn test_translate_paragraph_with_leading_newlines() {
+ let catalog = create_catalog(&[("foo bar", "FOO BAR")]);
+ assert_eq!(translate("\n\n\nfoo bar\n", &catalog), "\n\n\nFOO BAR\n");
+ }
+
+ #[test]
+ fn test_translate_paragraph_with_trailing_newlines() {
+ let catalog = create_catalog(&[("foo bar", "FOO BAR")]);
+ assert_eq!(translate("foo bar\n\n\n", &catalog), "FOO BAR\n\n\n");
+ }
+
+ #[test]
+ fn test_translate_multiple_paragraphs() {
+ let catalog = create_catalog(&[("foo bar", "FOO BAR")]);
+ assert_eq!(
+ translate(
+ "first paragraph\n\
+ \n\
+ foo bar\n\
+ \n\
+ last paragraph\n",
+ &catalog
+ ),
+ "first paragraph\n\
+ \n\
+ FOO BAR\n\
+ \n\
+ last paragraph\n"
+ );
+ }
+
+ #[test]
+ fn test_translate_multiple_paragraphs_extra_newlines() {
+ // Notice how the translated paragraphs have more lines.
+ let catalog = create_catalog(&[
+ (
+ "first\n\
+ paragraph",
+ "FIRST\n\
+ TRANSLATED\n\
+ PARAGRAPH",
+ ),
+ (
+ "last\n\
+ paragraph",
+ "LAST\n\
+ TRANSLATED\n\
+ PARAGRAPH",
+ ),
+ ]);
+ // Paragraph separation is kept intact while translating.
+ assert_eq!(
+ translate(
+ "\n\
+ first\n\
+ paragraph\n\
+ \n\
+ \n\
+ \n\
+ last\n\
+ paragraph\n\
+ \n\
+ \n",
+ &catalog
+ ),
+ "\n\
+ FIRST\n\
+ TRANSLATED\n\
+ PARAGRAPH\n\
+ \n\
+ \n\
+ \n\
+ LAST\n\
+ TRANSLATED\n\
+ PARAGRAPH\n\
+ \n\
+ \n"
+ );
+ }
+}
diff --git a/i18n-helpers/src/bin/mdbook-xgettext.rs b/i18n-helpers/src/bin/mdbook-xgettext.rs
new file mode 100644
index 00000000..630c8d95
--- /dev/null
+++ b/i18n-helpers/src/bin/mdbook-xgettext.rs
@@ -0,0 +1,128 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! `xgettext` for `mdbook`
+//!
+//! This program works like `xgettext`, meaning it will extract
+//! translatable strings from your book. The strings are saved in a
+//! GNU Gettext `messages.pot` file in your build directory (typically
+//! `po/messages.pot`).
+//!
+//! See `TRANSLATIONS.md` in the repository root for more information.
+
+use anyhow::{anyhow, Context};
+use mdbook::renderer::RenderContext;
+use mdbook::BookItem;
+use polib::catalog::Catalog;
+use polib::message::Message;
+use std::fs;
+use std::io;
+
+fn add_message(catalog: &mut Catalog, msgid: &str, source: &str) {
+ let sources = match catalog.find_message(msgid) {
+ Some(msg) => format!("{}\n{}", msg.source, source),
+ None => String::from(source),
+ };
+ let message = Message::new_singular("", &sources, "", "", msgid, "");
+
+ // Carefully update the existing message or add a new one. It's an
+ // error to create a catalog with duplicate msgids.
+ match catalog.find_message_index(msgid) {
+ Some(&idx) => catalog.update_message_by_index(idx, message).unwrap(),
+ None => catalog.add_message(message),
+ }
+}
+
+fn create_catalog(ctx: &RenderContext) -> anyhow::Result {
+ let mut catalog = Catalog::new();
+ if let Some(title) = &ctx.config.book.title {
+ catalog.metadata.project_id_version = String::from(title);
+ }
+ if let Some(lang) = &ctx.config.book.language {
+ catalog.metadata.language = String::from(lang);
+ }
+ catalog.metadata.mime_version = String::from("1.0");
+ catalog.metadata.content_type = String::from("text/plain; charset=UTF-8");
+ catalog.metadata.content_transfer_encoding = String::from("8bit");
+
+ let summary_path = ctx.config.book.src.join("SUMMARY.md");
+ let summary = std::fs::read_to_string(ctx.root.join(&summary_path))?;
+
+ // First, add all chapter names and part titles from SUMMARY.md.
+ // The book items are in order of the summary, so we can assign
+ // correct line numbers for duplicate lines by tracking the index
+ // of our last search.
+ let mut last_idx = 0;
+ for item in ctx.book.iter() {
+ let line = match item {
+ BookItem::Chapter(chapter) => &chapter.name,
+ BookItem::PartTitle(title) => title,
+ BookItem::Separator => continue,
+ };
+
+ let idx = summary[last_idx..].find(line).ok_or_else(|| {
+ anyhow!(
+ "Could not find {line:?} in SUMMARY.md after line {} -- \
+ please remove any formatting from SUMMARY.md",
+ summary[..last_idx].lines().count()
+ )
+ })?;
+ last_idx += idx;
+ let lineno = summary[..last_idx].lines().count();
+ let source = format!("{}:{}", summary_path.display(), lineno);
+ add_message(&mut catalog, line, &source);
+ }
+
+ // Next, we add the chapter contents.
+ for item in ctx.book.iter() {
+ if let BookItem::Chapter(chapter) = item {
+ let path = match &chapter.path {
+ Some(path) => ctx.config.book.src.join(path),
+ None => continue,
+ };
+ for (lineno, paragraph) in i18n_helpers::extract_paragraphs(&chapter.content)
+ {
+ let source = format!("{}:{}", path.display(), lineno);
+ add_message(&mut catalog, paragraph, &source);
+ }
+ }
+ }
+
+ Ok(catalog)
+}
+
+fn main() -> anyhow::Result<()> {
+ let ctx = RenderContext::from_json(&mut io::stdin()).context("Parsing stdin")?;
+ let cfg = ctx
+ .config
+ .get_renderer("xgettext")
+ .ok_or_else(|| anyhow!("Could not read output.xgettext configuration"))?;
+ let path = cfg
+ .get("pot-file")
+ .ok_or_else(|| anyhow!("Missing output.xgettext.pot-file config value"))?
+ .as_str()
+ .ok_or_else(|| anyhow!("Expected a string for output.xgettext.pot-file"))?;
+ fs::create_dir_all(&ctx.destination)
+ .with_context(|| format!("Could not create {}", ctx.destination.display()))?;
+ let output_path = ctx.destination.join(path);
+ if output_path.exists() {
+ fs::remove_file(&output_path)
+ .with_context(|| format!("Removing {}", output_path.display()))?
+ }
+ let catalog = create_catalog(&ctx).context("Extracting messages")?;
+ polib::po_file::write(&catalog, &output_path)
+ .with_context(|| format!("Writing messages to {}", output_path.display()))?;
+
+ Ok(())
+}
diff --git a/i18n-helpers/src/lib.rs b/i18n-helpers/src/lib.rs
new file mode 100644
index 00000000..fa3e6e6f
--- /dev/null
+++ b/i18n-helpers/src/lib.rs
@@ -0,0 +1,123 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use once_cell::sync::Lazy;
+use regex::Regex;
+
+static PARAGRAPH_SEPARATOR: Lazy = Lazy::new(|| Regex::new(r"\n\n+").unwrap());
+
+/// Extract paragraphs from text.
+///
+/// Paragraphs are separated by at least two newlines. Returns an
+/// iterator over line numbers (starting from 1) and paragraphs.
+pub fn extract_paragraphs(text: &str) -> impl Iterator {
+ // TODO: This could be made more sophisticated by parsing the
+ // Markdown and stripping off the markup characters.
+ //
+ // As an example, a header like "## My heading" could become just
+ // "My heading" in the `.pot` file. Similarly, paragraphs could be
+ // unfolded and list items could be translated one-by-one.
+
+ // Skip over leading empty lines.
+ let trimmed = text.trim_start_matches('\n');
+ let mut matches = PARAGRAPH_SEPARATOR.find_iter(trimmed);
+ let mut lineno = 1 + text.len() - trimmed.len();
+ let mut last = 0;
+
+ std::iter::from_fn(move || match matches.next() {
+ Some(m) => {
+ let result = (lineno, &trimmed[last..m.start()]);
+ lineno += trimmed[last..m.end()].lines().count();
+ last = m.end();
+ Some(result)
+ }
+ None => {
+ if last < trimmed.len() {
+ let result = (lineno, trimmed[last..].trim_end_matches('\n'));
+ last = trimmed.len();
+ Some(result)
+ } else {
+ None
+ }
+ }
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ macro_rules! assert_iter_eq {
+ ($left_iter:expr, $right:expr) => {
+ assert_eq!($left_iter.collect::>(), $right)
+ };
+ }
+
+ #[test]
+ fn test_extract_paragraphs_empty() {
+ assert_iter_eq!(extract_paragraphs(""), vec![]);
+ }
+
+ #[test]
+ fn test_extract_paragraphs_single_line() {
+ assert_iter_eq!(
+ extract_paragraphs("This is a paragraph."),
+ vec![(1, "This is a paragraph.")]
+ );
+ }
+
+ #[test]
+ fn test_extract_paragraphs_simple() {
+ assert_iter_eq!(
+ extract_paragraphs(
+ "This is\n\
+ the first\n\
+ paragraph.\n\
+ \n\
+ Second paragraph."
+ ),
+ vec![
+ (1, "This is\nthe first\nparagraph."),
+ (5, "Second paragraph.")
+ ]
+ );
+ }
+
+ #[test]
+ fn test_extract_paragraphs_leading_newlines() {
+ assert_iter_eq!(
+ extract_paragraphs(
+ "\n\
+ \n\
+ \n\
+ This is the\n\
+ first paragraph."
+ ),
+ vec![(4, "This is the\nfirst paragraph.")]
+ );
+ }
+
+ #[test]
+ fn test_extract_paragraphs_trailing_newlines() {
+ assert_iter_eq!(
+ extract_paragraphs(
+ "This is\n\
+ a paragraph.\n\
+ \n\
+ \n"
+ ),
+ vec![(1, "This is\na paragraph.")]
+ );
+ }
+}
diff --git a/po/it.po b/po/it.po
new file mode 100644
index 00000000..190093e9
--- /dev/null
+++ b/po/it.po
@@ -0,0 +1,5605 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: GB ASM Tutorial\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: 2023-03-02 23:18+0100\n"
+"Last-Translator: avivace4@gmail.com\n"
+"Language-Team: \n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#: src/SUMMARY.md:1
+msgid "Home"
+msgstr ""
+
+#: src/SUMMARY.md:2
+msgid "Roadmap"
+msgstr ""
+
+#: src/SUMMARY.md:3
+msgid "Help and feedback"
+msgstr ""
+
+#: src/SUMMARY.md:5
+msgid "Part Ⅰ — Hello World!"
+msgstr ""
+
+#: src/SUMMARY.md:7
+msgid "Setup"
+msgstr ""
+
+#: src/SUMMARY.md:8
+msgid "Hello World!"
+msgstr ""
+
+#: src/SUMMARY.md:9
+msgid "The toolchain"
+msgstr ""
+
+#: src/SUMMARY.md:10
+msgid "Binary and hexadecimal"
+msgstr ""
+
+#: src/SUMMARY.md:11
+msgid "Registers"
+msgstr ""
+
+#: src/SUMMARY.md:12
+msgid "Assembly basics"
+msgstr ""
+
+#: src/SUMMARY.md:13
+msgid "Memory"
+msgstr "Memoria"
+
+#: src/SUMMARY.md:14
+msgid "The header"
+msgstr ""
+
+#: src/SUMMARY.md:15
+msgid "Operations & flags"
+msgstr ""
+
+#: src/SUMMARY.md:16
+msgid "Jumps"
+msgstr ""
+
+#: src/SUMMARY.md:17
+msgid "Tracing"
+msgstr ""
+
+#: src/SUMMARY.md:18
+msgid "Graphics"
+msgstr ""
+
+#: src/SUMMARY.md:19
+msgid "Tiles"
+msgstr ""
+
+#: src/SUMMARY.md:20
+msgid "Palettes"
+msgstr ""
+
+#: src/SUMMARY.md:21
+msgid "Tilemap"
+msgstr ""
+
+#: src/SUMMARY.md:22
+msgid "Wrapping up"
+msgstr ""
+
+#: src/SUMMARY.md:24
+msgid "Part Ⅱ — Our first game"
+msgstr ""
+
+#: src/SUMMARY.md:26
+msgid "Getting started"
+msgstr ""
+
+#: src/SUMMARY.md:27
+msgid "Objects"
+msgstr ""
+
+#: src/SUMMARY.md:28
+msgid "Functions"
+msgstr ""
+
+#: src/SUMMARY.md:29
+msgid "Input"
+msgstr ""
+
+#: src/SUMMARY.md:30
+msgid "Collision"
+msgstr ""
+
+#: src/SUMMARY.md:31
+msgid "Work in progress"
+msgstr ""
+
+#: src/SUMMARY.md:33
+msgid "Part Ⅲ — Our second game"
+msgstr ""
+
+#: src/SUMMARY.md:35
+msgid "To be written..."
+msgstr ""
+
+#: src/SUMMARY.md:39
+msgid "Where to go next"
+msgstr ""
+
+#: src/SUMMARY.md:40
+msgid "Resources"
+msgstr ""
+
+#: src/SUMMARY.md:41
+msgid "Thanks"
+msgstr ""
+
+#: src/index.md:1
+msgid "# Home"
+msgstr ""
+
+#: src/index.md:3
+msgid ""
+"👋 Welcome to gb-asm-tutorial!\n"
+"This tutorial will teach you how to make games for the Game Boy and Game Boy "
+"Color."
+msgstr ""
+
+#: src/index.md:6 src/part1/hello_world.md:15 src/part1/registers.md:24
+msgid "::: warning:⚠️"
+msgstr ""
+
+#: src/index.md:8
+msgid ""
+"While the Game Boy and Game Boy Color are almost the same console, **the "
+"Game Boy Advance is entirely different**.\n"
+"However, the GBA is able to run GB and GBC games!\n"
+"If you are looking to program GBC games and run them on a GBA, you're at the "
+"right place; however, if you want to make games specifically for the GBA, "
+"please check out [Tonc](http://coranac.com/tonc/text) instead."
+msgstr ""
+
+#: src/index.md:12 src/roadmap.md:19 src/part1/setup.md:17
+#: src/part1/setup.md:55 src/part1/hello_world.md:19
+#: src/part1/hello_world.md:46 src/part1/hello_world.md:70
+#: src/part1/bin_and_hex.md:28 src/part1/bin_and_hex.md:109
+#: src/part1/registers.md:29 src/part1/registers.md:44 src/part1/assembly.md:65
+#: src/part1/assembly.md:72 src/part1/assembly.md:176 src/part1/memory.md:8
+#: src/part1/memory.md:92 src/part1/memory.md:110 src/part1/header.md:22
+#: src/part1/header.md:56 src/part1/header.md:93 src/part1/header.md:140
+#: src/part1/operations.md:81 src/part1/jumps.md:7 src/part1/jumps.md:39
+#: src/part1/jumps.md:66 src/part1/jumps.md:90 src/part1/tracing.md:36
+#: src/part1/tracing.md:53 src/part1/tracing.md:65 src/part1/tracing.md:76
+#: src/part1/tiles.md:13 src/part1/tiles.md:40 src/part1/palettes.md:13
+#: src/part1/palettes.md:26 src/part1/tilemap.md:8 src/part1/tilemap.md:45
+#: src/part1/wrapup.md:16 src/part2/getting-started.md:30
+#: src/part2/getting-started.md:113 src/part2/getting-started.md:149
+#: src/part2/objects.md:13 src/part2/objects.md:40 src/part2/objects.md:165
+#: src/part2/collision.md:131 src/part2/collision.md:285
+#: src/part2/collision.md:415 src/part2/wip.md:13
+msgid ":::"
+msgstr ""
+
+#: src/index.md:14
+msgid "## Controls"
+msgstr ""
+
+#: src/index.md:16
+msgid "There are some handy icons near the top of your screen!"
+msgstr ""
+
+#: src/index.md:18
+msgid ""
+"- The \"burger\" toggles the navigation side "
+"panel;\n"
+"- The brush allows selecting a different "
+"color theme;\n"
+"- The magnifying glass pops up a search bar;\n"
+"- The printer gives a single-page version of "
+"the *entire* tutorial, which you can print if you want;\n"
+"- The GitHub icon links to the tutorial's "
+"source repository;\n"
+"- The edit button allows you to suggest changes "
+"to the tutorial, provided that you have a GitHub account."
+msgstr ""
+
+#: src/index.md:25
+msgid ""
+"Additionally, there are arrows to the left and to the right of the page "
+"(they are at the bottom instead on mobile) to more easily navigate to the "
+"next page."
+msgstr ""
+
+#: src/index.md:27
+msgid ""
+"With that said, you can get started by simply navigating to the following "
+"page :)"
+msgstr ""
+
+#: src/index.md:29
+msgid "## Licensing"
+msgstr ""
+
+#: src/index.md:31
+msgid "**In short**:"
+msgstr ""
+
+#: src/index.md:33
+msgid ""
+"- Code within the tutorial is essentially **public domain**, meaning that "
+"you are allowed to copy it freely without restrictions.\n"
+"- You are free to copy the tutorial's contents (prose, diagrams, etc.), "
+"modify them, and share that, but you must give credit and license any copies "
+"permissively.\n"
+"- This site's *source code* can be freely copied, but you must give a "
+"license and copyright notice."
+msgstr ""
+
+#: src/index.md:37
+msgid ""
+"**Full details**, please follow these links for more information on the "
+"respective licenses:"
+msgstr ""
+
+#: src/index.md:39
+msgid ""
+"- All the code contained within the tutorial itself is licensed under CC0. *To the extent possible under law, all copyright and related or "
+"neighboring rights to code presented within GB ASM Tutorial have been "
+"waived. This work is published from France.*\n"
+"- The contents (prose, images, etc.) of this tutorial are licensed under a "
+"Creative Commons Attribution-ShareAlike 4.0 International License.\n"
+"- Code used to display and format the site is licensed under the [MIT "
+"License](https://github.com/ISSOtm/gb-asm-tutorial/blob/master/LICENSE) "
+"unless otherwise specified."
+msgstr ""
+
+#: src/roadmap.md:1
+msgid "# Roadmap"
+msgstr ""
+
+#: src/roadmap.md:3
+msgid ""
+"The tutorial is split into three sections.\n"
+"**I strongly advise you go through the tutorial in order!**"
+msgstr ""
+
+#: src/roadmap.md:6
+msgid ""
+"In Part Ⅰ, we run our first \"Hello World!\" program, which we then dissect "
+"to learn what makes the Game Boy tick."
+msgstr ""
+
+#: src/roadmap.md:8
+msgid ""
+"In Part Ⅱ, we program our first game, a clone of *Arkanoid*; we learn how to "
+"prod the hardware into having something we can call a \"game\".\n"
+"Along the way, we will make plenty of mistakes, so we can learn how to debug "
+"our code."
+msgstr ""
+
+#: src/roadmap.md:11
+msgid ""
+"And finally, Part Ⅲ is about \"advanced\" use of the hardware, where we "
+"learn how to make even better-looking games, and we program a Shoot 'Em Up!"
+msgstr ""
+
+#: src/roadmap.md:13 src/part1/setup.md:50 src/part1/assembly.md:169
+#: src/part1/memory.md:87 src/part1/memory.md:106 src/part1/header.md:18
+#: src/part1/jumps.md:3 src/part1/jumps.md:61 src/part1/tilemap.md:39
+#: src/part2/getting-started.md:24 src/part2/getting-started.md:144
+#: src/part2/objects.md:8 src/part2/objects.md:33 src/part2/collision.md:125
+msgid "::: tip"
+msgstr ""
+
+#: src/roadmap.md:15
+msgid ""
+"I hope the tutorial will work for you!!\n"
+"But if it doesn't (the format doesn't work well for everyone, and that's "
+"okay), I encourage you to look at [some other resources](resources.md), "
+"which might work better for you.\n"
+"It's also fine to take a break from time to time; feel free to read at your "
+"own pace, and to [ask for clarifications](https://gbdev.io/chat) if anything "
+"isn't clear to you."
+msgstr ""
+
+#: src/roadmap.md:21 src/part1/assembly.md:181 src/part1/memory.md:134
+#: src/part1/jumps.md:114 src/part1/tracing.md:140 src/part1/tiles.md:96
+#: src/part1/palettes.md:106 src/part1/tilemap.md:67 src/part1/wrapup.md:18
+#: src/thanks.md:11
+msgid "---"
+msgstr ""
+
+#: src/roadmap.md:23
+msgid ""
+"Unfortunately, this tutorial is a work in progress.\n"
+"Currently, **Part Ⅱ is being written**, and Part Ⅲ should follow soon after."
+msgstr ""
+
+#: src/help-feedback.md:1
+msgid "# Help and feedback"
+msgstr ""
+
+#: src/help-feedback.md:3
+msgid ""
+"If you are stuck in a certain part of the tutorial, want some advice, or "
+"just wish to chat with us, [the GBDev community chat](https://gbdev.io/chat) "
+"is the place to go!\n"
+"I actively participate there, and don't be afraid to ask questions!\n"
+"(The \"ASM\" channel should be the most appropriate to discuss the tutorial, "
+"by the way.)"
+msgstr ""
+
+#: src/help-feedback.md:7
+msgid ""
+"If you are interested in contributing to or translating the tutorial, thank "
+"you!\n"
+"Follow the link above, I'd be happy to work with you."
+msgstr ""
+
+#: src/help-feedback.md:10
+msgid ""
+"Noticed a problem with the tutorial?\n"
+"Please [check out our issue tracker](https://github.com/ISSOtm/gb-asm-"
+"tutorial/issues); if there is no open issue about your problem, please "
+"create a new one, or reach out to us via the link above."
+msgstr ""
+
+#: src/help-feedback.md:13
+msgid ""
+"If you prefer email, my address is `tutorial@`, where you replace "
+"`` with this website's domain name (it ends with `.fr`).\n"
+"Anti-spam measure, I hope you understand."
+msgstr ""
+
+#: src/part1/setup.md:1
+msgid "# Setup"
+msgstr ""
+
+#: src/part1/setup.md:3
+msgid ""
+"First, we should set up our dev environment.\n"
+"We will need:"
+msgstr ""
+
+#: src/part1/setup.md:6
+msgid ""
+"1. A POSIX environment\n"
+"2. [RGBDS](https://rgbds.gbdev.io/install) v0.5.1 (though v0.5.0 should be "
+"compatible)\n"
+"3. GNU Make (preferably a recent version)\n"
+"4. A code editor\n"
+"5. A debugging emulator"
+msgstr ""
+
+#: src/part1/setup.md:12
+msgid "::: tip:❓😕"
+msgstr ""
+
+#: src/part1/setup.md:14
+msgid ""
+"The following install instructions are provided on a \"best-effort\" basis, "
+"but may be outdated, or not work for you for some reason.\n"
+"Don't worry, we're here to help: [ask away in GBDev](../index.md#feedback), "
+"and we'll help you with installing everything!"
+msgstr ""
+
+#: src/part1/setup.md:19
+msgid "## Tools"
+msgstr ""
+
+#: src/part1/setup.md:21
+msgid "### Linux & macOS"
+msgstr ""
+
+#: src/part1/setup.md:23
+msgid ""
+"Good news: you're already fulfilling step 1!\n"
+"You just need to [install RGBDS](https://rgbds.gbdev.io/install), and maybe "
+"update GNU Make."
+msgstr ""
+
+#: src/part1/setup.md:26
+msgid "#### macOS"
+msgstr ""
+
+#: src/part1/setup.md:28
+msgid ""
+"At the time of writing this, macOS (up to 11.0, the current latest release) "
+"ships a very outdated GNU Make.\n"
+"You can check it by opening a terminal, and running `make --version`, which "
+"should indicate \"GNU Make\" and a date, among other things."
+msgstr ""
+
+#: src/part1/setup.md:31
+msgid ""
+"If your Make is too old, you can update it using [Homebrew](https://brew."
+"sh)'s formula [`make`](https://formulae.brew.sh/formula/make#default).\n"
+"At the time of writing, this should print a warning that the updated Make "
+"has been installed as `gmake`; you can either follow the suggestion to use "
+"it as your \"default\" `make`, or use `gmake` instead of `make` in this "
+"tutorial."
+msgstr ""
+
+#: src/part1/setup.md:34
+msgid "#### Linux"
+msgstr ""
+
+#: src/part1/setup.md:36
+msgid ""
+"Once RGBDS is installed, open a terminal and run `make --version` to check "
+"your Make version (which is likely GNU Make)."
+msgstr ""
+
+#: src/part1/setup.md:38
+msgid ""
+"If `make` cannot be found, you may need to install your distribution's "
+"`build-essentials`."
+msgstr ""
+
+#: src/part1/setup.md:40
+msgid "### Windows"
+msgstr ""
+
+#: src/part1/setup.md:42
+msgid ""
+"The sad truth is that Windows is a terrible OS for development; however, you "
+"can install environments that solve most issues."
+msgstr ""
+
+#: src/part1/setup.md:44
+msgid ""
+"On Windows 10, your best bet is [WSL](https://docs.microsoft.com/en-us/"
+"windows/wsl), which sort of allows running a Linux distribution within "
+"Windows.\n"
+"Install WSL 1 or WSL 2, then a distribution of your choice, and then follow "
+"these steps again, but for the Linux distribution you installed."
+msgstr ""
+
+#: src/part1/setup.md:47
+msgid ""
+"If WSL is not an option, you can use [MSYS2](https://www.msys2.org) or "
+"[Cygwin](https://www.cygwin.com) instead; then check out [RGBDS' Windows "
+"install instructions](https://rgbds.gbdev.io/install).\n"
+"As far as I'm aware, both of these provide a sufficiently up-to-date version "
+"of GNU Make."
+msgstr ""
+
+#: src/part1/setup.md:52
+msgid ""
+"If you have programmed for other consoles, such as the GBA, check if MSYS2 "
+"isn't already installed on your machine.\n"
+"This is because devkitPro, a popular homebrew development bundle, includes "
+"MSYS2."
+msgstr ""
+
+#: src/part1/setup.md:57
+msgid "## Code editor"
+msgstr ""
+
+#: src/part1/setup.md:59
+msgid ""
+"Any code editor is fine; I personally use [Sublime Text](https://www."
+"sublimetext.com) with its [RGBDS syntax package](https://packagecontrol.io/"
+"packages/RGBDS); however, you can use any text editor, including Notepad, if "
+"you're crazy enough.\n"
+"Awesome GBDev has [a section on syntax highlighting packages](https://gbdev."
+"io/resources#syntax-highlighting-packages), see there if your favorite "
+"editor supports RGBDS."
+msgstr ""
+
+#: src/part1/setup.md:62
+msgid "## Emulator"
+msgstr ""
+
+#: src/part1/setup.md:64
+msgid ""
+"Using an emulator to play games is one thing; using it to program games is "
+"another.\n"
+"The two aspects an emulator must fulfill to allow an enjoyable programming "
+"experience are:\n"
+"- **Debugging tools**:\n"
+" When your code goes haywire on an actual console, it's very difficult to "
+"figure out why or how.\n"
+" There is no console output, no way to `gdb` the program, nothing.\n"
+" However, an emulator can provide debugging tools, allowing you to control "
+"execution, inspect memory, etc.\n"
+" These are vital if you want GB dev to be *fun*, trust me!\n"
+"- **Good accuracy**:\n"
+" Accuracy means \"how faithful to the original console something is\".\n"
+" Using a bad emulator for playing games can work (to some extent, and even "
+"then...), but using it for *developing* a game makes it likely to "
+"accidentally render your game incompatible with the actual console.\n"
+" For more info, read [this article on Ars Technica](https://arstechnica."
+"com/?post_type=post&p=44524) (especially the An emulator for every game"
+"q> section at the top of page 2).\n"
+" You can compare GB emulator accuracy on [Daid's GB-emulator-shootout]"
+"(https://daid.github.io/GBEmulatorShootout/)."
+msgstr ""
+
+#: src/part1/setup.md:77
+msgid ""
+"The emulator I will be using for this tutorial is [BGB](https://bgb.bircd."
+"org) (1.5.9 when I'm writing this).\n"
+"It's Windows-only, but macOS and Linux users can install Wine to be able to "
+"run it, and macOS users will additionally have to use the 64-bit version.\n"
+"Other debugging emulators are possible (such as SameBoy on macOS), but I "
+"will be giving directives for and including screenshots of BGB."
+msgstr ""
+
+#: src/part1/hello_world.md:1
+msgid "# Hello World!"
+msgstr ""
+
+#: src/part1/hello_world.md:3
+msgid ""
+"In this lesson, we will begin by assembling our first program.\n"
+"The rest of this chapter will be dedicated to explaining how and why it "
+"works."
+msgstr ""
+
+#: src/part1/hello_world.md:6
+msgid ""
+"Note that we will need to type a lot of commands, so open a terminal now.\n"
+"It's a good idea to create a new directory (`mkdir gb_hello_world`, for "
+"example, then `cd gb_hello_world` to enter the new directory)."
+msgstr ""
+
+#: src/part1/hello_world.md:9
+msgid ""
+"Grab the following files (right-click each link, \"Save Link As...\"), and "
+"place them all in this new directory:\n"
+"- [`hello-world.asm`](../assets/hello-world.asm)\n"
+"- [`hardware.inc`](https://raw.githubusercontent.com/gbdev/hardware.inc/v4.0/"
+"hardware.inc)"
+msgstr ""
+
+#: src/part1/hello_world.md:13
+msgid ""
+"Then, still from a terminal within that directory, run the following three "
+"commands."
+msgstr ""
+
+#: src/part1/hello_world.md:17
+msgid ""
+"To clarify where each individual command begins, I've added a `$` before "
+"each command, but don't type them!"
+msgstr ""
+
+#: src/part1/hello_world.md:21
+msgid ""
+"```console\n"
+"$ rgbasm -L -o hello-world.o hello-world.asm\n"
+"$ rgblink -o hello-world.gb hello-world.o\n"
+"$ rgbfix -v -p 0xFF hello-world.gb\n"
+"```"
+msgstr ""
+
+#: src/part1/hello_world.md:27
+msgid ""
+""
+msgstr ""
+
+#: src/part1/hello_world.md:33
+msgid "::: danger:‼️"
+msgstr ""
+
+#: src/part1/hello_world.md:35
+msgid ""
+"Be careful with arguments! Some options, such as `-o` here, use the argument "
+"after them as a parameter:"
+msgstr ""
+
+#: src/part1/hello_world.md:37
+msgid ""
+"1. `rgbasm -L -o hello-world.asm hello-world.o` won't work (and may corrupt "
+"`hello-world.asm`!)\n"
+"2. `rgbasm -L hello-world.asm -o hello-world.o` will work\n"
+"3. `rgbasm hello-world.asm -o hello-world.o -L` will also work"
+msgstr ""
+
+#: src/part1/hello_world.md:41
+msgid "If you need whitespace within an argument, you must quote it:"
+msgstr ""
+
+#: src/part1/hello_world.md:43
+msgid ""
+"1. `rgbasm -L -o hello world.o hello world.asm` won't work\n"
+"2. `rgbasm -L -o \"hello world.o\" \"hello world.asm\"` will work"
+msgstr ""
+
+#: src/part1/hello_world.md:48
+msgid ""
+"It should look like this:\n"
+""
+msgstr ""
+
+#: src/part1/hello_world.md:51
+msgid ""
+"(If you encounter an error you can't figure out by yourself, don't be afraid "
+"to [ask us](../index.md#feedback)! We'll sort it out.)"
+msgstr ""
+
+#: src/part1/hello_world.md:53
+msgid ""
+"Congrats!\n"
+"You just assembled your first Game Boy ROM!\n"
+"Now, we just need to run it; open BGB, right-click the window that opened, "
+"and load `hello-world.gb`."
+msgstr ""
+
+#: src/part1/hello_world.md:57
+msgid ""
+""
+msgstr ""
+
+#: src/part1/hello_world.md:64
+msgid "::: danger:🤕"
+msgstr ""
+
+#: src/part1/hello_world.md:66
+msgid ""
+"If you are using Wine and BGB is failing to start (or gives a blank/black "
+"screen), [try these troubleshooting tips](https://eldred.fr/bgb#getting-"
+"started-on-wine)."
+msgstr ""
+
+#: src/part1/hello_world.md:68
+msgid "And in any case, don't hesitate to ask for help!"
+msgstr ""
+
+#: src/part1/hello_world.md:72
+msgid ""
+"You could also take a flash cart (I use the [EverDrive GB X5](https://krikzz."
+"com/store/home/47-everdrive-gb.html), but there are plenty of alternatives), "
+"load up your ROM onto it, and run it on an actual console!"
+msgstr ""
+
+#: src/part1/hello_world.md:74
+msgid ""
+""
+msgstr ""
+
+#: src/part1/hello_world.md:76
+msgid ""
+"Well, now that we have something working, it's time to peel back the "
+"curtains..."
+msgstr ""
+
+#: src/part1/toolchain.md:1
+msgid "# The toolchain"
+msgstr ""
+
+#: src/part1/toolchain.md:3
+msgid ""
+"So, in the previous lesson, we built a nice little \"Hello World!\" ROM.\n"
+"Now, let's find out exactly what we did."
+msgstr ""
+
+#: src/part1/toolchain.md:6
+msgid "## RGBASM and RGBLINK"
+msgstr ""
+
+#: src/part1/toolchain.md:8
+msgid "Let's begin by explaining what `rgbasm` and `rgblink` do."
+msgstr ""
+
+#: src/part1/toolchain.md:10
+msgid ""
+"RGBASM is an *assembler*.\n"
+"It is responsible for reading the source code (in our case, `hello-world."
+"asm` and `hardware.inc`), and generating blocks of code with some "
+"\"holes\".\n"
+"RGBASM does not always have enough information to produce a full ROM, so it "
+"does most of the work, and stores its intermediary results in what's known "
+"as *object files* (hence the `.o` extension)."
+msgstr ""
+
+#: src/part1/toolchain.md:14
+msgid ""
+"RGBLINK is a *linker*.\n"
+"Its job is taking object files (or, like in our case, just one), and "
+"\"linking\" them into a ROM, which is to say: filling the aforementioned "
+"\"holes\".\n"
+"RGBLINK's purpose may not be obvious with programs as simple as this Hello "
+"World, but it will become much clearer in Part Ⅱ."
+msgstr ""
+
+#: src/part1/toolchain.md:18
+msgid ""
+"So: Source code → `rgbasm` → Object files → `rgblink` → ROM, right?\n"
+"Well, not exactly."
+msgstr ""
+
+#: src/part1/toolchain.md:21 src/part1/header.md:60
+msgid "## RGBFIX"
+msgstr ""
+
+#: src/part1/toolchain.md:23
+msgid ""
+"RGBLINK does produces a ROM, but it's not quite usable yet.\n"
+"See, actual ROMs have what's called a *header*.\n"
+"It's a special area of the ROM that contains [metadata about the ROM]"
+"(https://gbdev.io/pandocs/The_Cartridge_Header.html); for example, the "
+"game's name, Game Boy Color compatibility, and more.\n"
+"For simplicity, we defaulted a lot of these values to 0 for the time being; "
+"we'll come back to them in Part Ⅱ."
+msgstr ""
+
+#: src/part1/toolchain.md:28
+msgid ""
+"However, the header contains three crucial fields:\n"
+"- The [Nintendo logo](https://gbdev.io/pandocs/The_Cartridge_Header."
+"html#0104-0133--nintendo-logo),\n"
+"- the [ROM's size](https://gbdev.io/pandocs/The_Cartridge_Header.html#0148--"
+"rom-size),\n"
+"- and [two checksums](https://gbdev.io/pandocs/The_Cartridge_Header."
+"html#014d--header-checksum)."
+msgstr ""
+
+#: src/part1/toolchain.md:33
+msgid ""
+"When the console first starts up, it runs [a little program](https://github."
+"com/ISSOtm/gb-bootroms) known as the *boot ROM*, which reads and draws the "
+"logo from the cartridge, and displays the little boot animation.\n"
+"When the animation is finished, the console checks if the logo matches a "
+"copy that it stores internally; if there is a mismatch, **it locks up!**\n"
+"And, since it locks up, our game never gets to run... 😦\n"
+"This was meant as an anti-piracy measure; however, that measure [has since "
+"then been ruled as invalid](https://en.wikipedia.org/wiki/Sega_v._Accolade), "
+"so don't worry, we are clear! 😄"
+msgstr ""
+
+#: src/part1/toolchain.md:38
+msgid ""
+"Similarly, the boot ROM also computes a *[checksum](https://en.wikipedia.org/"
+"wiki/Checksum)* of the header, supposedly to ensure that it isn't "
+"corrupted.\n"
+"The header also contains a copy of this checksum; if it doesn't match what "
+"the boot ROM computed, then the boot ROM **also locks up!**"
+msgstr ""
+
+#: src/part1/toolchain.md:41
+msgid ""
+"The header also contains a checksum over the whole ROM, but nothing ever "
+"uses it.\n"
+"It doesn't hurt to get it right, though."
+msgstr ""
+
+#: src/part1/toolchain.md:44
+msgid ""
+"Finally, the header also contains the ROM's size, which is required by "
+"emulators and flash carts."
+msgstr ""
+
+#: src/part1/toolchain.md:46
+msgid ""
+"RGBFIX's role is to fill in the header, especially these 3 fields, which are "
+"required for our ROM to be guaranteed to run fine.\n"
+"The `-v` option instructs RGBFIX to make the header **v**alid, by injecting "
+"the Nintendo logo and computing the two checksums.\n"
+"The `-p 0xFF` option instructs it to **p**ad the ROM to a valid size, and "
+"set the corresponding value in the \"ROM size\" header field."
+msgstr ""
+
+#: src/part1/toolchain.md:50
+msgid ""
+"Alright!\n"
+"So the full story is: Source code → `rgbasm` → Object files → `rgblink` → "
+"\"Raw\" ROM → `rgbfix` → \"Fixed\" ROM.\n"
+"Good."
+msgstr ""
+
+#: src/part1/toolchain.md:54
+msgid ""
+"You might be wondering why RGBFIX's functionality hasn't been included "
+"directly in RGBLINK.\n"
+"There are some historical reasons, but RGBLINK can also be used to produce "
+"things other than ROMs (especially via the `-x` option), and RGBFIX is "
+"sometimes used without RGBLINK anywhere in sight."
+msgstr ""
+
+#: src/part1/toolchain.md:57
+msgid "## File names"
+msgstr ""
+
+#: src/part1/toolchain.md:59
+msgid ""
+"Note that RGBDS does not care at all about the files' extensions.\n"
+"Some people call their source code `.s`, for example, or their object files "
+"`.obj`.\n"
+"The file names don't matter, either; it's just practical to keep the same "
+"name."
+msgstr ""
+
+#: src/part1/bin_and_hex.md:1
+msgid "# Binary and hexadecimal"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:3
+msgid ""
+"Before we talk about the code, a bit of background knowledge is in order.\n"
+"When programming at a low level, understanding of *[binary](https://en."
+"wikipedia.org/wiki/Binary_number)* and *[hexadecimal](https://en.wikipedia."
+"org/wiki/Hexadecimal)* is mandatory.\n"
+"Since you may already know about both of these, a summary of the RGBDS-"
+"specific information is available at the end of this lesson."
+msgstr ""
+
+#: src/part1/bin_and_hex.md:7
+msgid ""
+"So, what's binary?\n"
+"It's a different way to represent numbers, in what's called *base 2*.\n"
+"We're used to counting in [base 10](https://en.wikipedia.org/wiki/Decimal), "
+"so we have 10 digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9.\n"
+"Here's how digits work:"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:12
+msgid ""
+"```\n"
+" 42 = 4 × 10 + 2\n"
+" = 4 × 10^1 + 2 × 10^0\n"
+" ↑ ↑\n"
+" These tens come from us counting in base 10!"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:18
+msgid ""
+"1024 = 1 × 1000 + 0 × 100 + 2 × 10 + 4\n"
+" = 1 × 10^3 + 0 × 10^2 + 2 × 10^1 + 4 × 10^0\n"
+" ↑ ↑ ↑ ↑\n"
+"And here we can see the digits that make up the number!\n"
+"```"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:24 src/part1/registers.md:40
+#: src/part1/assembly.md:61 src/part1/tracing.md:72
+msgid "::: tip:ℹ️"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:26
+msgid ""
+"`^` here means \"to the power of\", where `X^N` is equal to multiplying `X` "
+"with itself `N` times, and `X ^ 0 = 1`."
+msgstr ""
+
+#: src/part1/bin_and_hex.md:30
+msgid ""
+"Decimal digits form a unique *decomposition* of numbers in powers of 10 "
+"(*deci*mal is base 10, remember?).\n"
+"But why stop at powers of 10?\n"
+"We could use other bases instead, such as base 2.\n"
+"(Why base 2 specifically will be explained later.)"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:35
+msgid ""
+"Binary is base 2, so there are only two digits, called *bits*: 0 and 1.\n"
+"Thus, we can generalize the principle outlined above, and write these two "
+"numbers in a similar way:"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:38
+msgid ""
+"```\n"
+" 42 = 1 × 32 + 0 × 16 "
+"+ 1 × 8 + 0 × 4 + 1 × 2 + 0\n"
+" = 1 × 2^5 + 0 × 2^4 "
+"+ 1 × 2^3 + 0 × 2^2 + 1 × 2^1 + 0 × 2^0\n"
+" ↑ "
+"↑ ↑ ↑ ↑ ↑\n"
+" And since now we're counting in "
+"base 2, we're seeing twos instead of tens!"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:44
+msgid ""
+"1024 = 1 × 1024 + 0 × 512 + 0 × 256 + 0 × 128 + 0 × 64 + 0 × 32 + 0 × 16 "
+"+ 0 × 8 + 0 × 4 + 0 × 2 + 0\n"
+" = 1 × 2^10 + 0 × 2^9 + 0 × 2^8 + 0 × 2^7 + 0 × 2^6 + 0 × 2^5 + 0 × 2^4 "
+"+ 0 × 2^3 + 0 × 2^2 + 0 × 2^1 + 0 × 2^0\n"
+" ↑ ↑ ↑ ↑ ↑ ↑ "
+"↑ ↑ ↑ ↑ ↑\n"
+"```"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:49
+msgid ""
+"So, by applying the same principle, we can say that in base 2, 42 is written "
+"as `101010`, and 1024 as `10000000000`. \n"
+"Since you can't tell ten (decimal 10) and two (binary 10) apart, RGBDS "
+"assembly has binary numbers prefixed by a percent sign: 10 is ten, and %10 "
+"is two."
+msgstr ""
+
+#: src/part1/bin_and_hex.md:52
+msgid ""
+"Okay, but why base 2 specifically?\n"
+"Rather conveniently, a bit can only be 0 or 1, which are easy to represent "
+"as \"ON\" or \"OFF\", empty or full, etc!\n"
+"If you want, at home, to create a one-bit memory, just take a box.\n"
+"If it's empty, it stores a 0; if it contains *something*, it stores a 1.\n"
+"Computers thus primarily manipulate binary numbers, and this has a *slew* of "
+"implications, as we will see throughout this entire tutorial."
+msgstr ""
+
+#: src/part1/bin_and_hex.md:58
+msgid "## Hexadecimal"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:60
+msgid ""
+"To recap, decimal isn't practical for a computer to work with, instead "
+"relying on binary (base 2) numbers.\n"
+"Okay, but binary is really impractical to work with.\n"
+"Take %10000000000, aka 2048; when in decimal only 4 digits are required, "
+"binary instead needs 12!\n"
+"And, did you notice that I actually wrote one zero too few?\n"
+"Fortunately, hexadecimal is here to save the day! 🦸"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:66
+msgid ""
+"Base 16 works just the same as every other base, but with 16 digits, called "
+"*nibbles*: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F."
+msgstr ""
+
+#: src/part1/bin_and_hex.md:68
+msgid ""
+"```\n"
+" 42 = 2 × 16 + 10\n"
+" = 2 × 16^1 + A × 16^0"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:72
+msgid ""
+"1024 = 4 × 256 + 0 × 16 + 0\n"
+" = 4 × 16^2 + 0 × 16^1 + 0 × 16^0\n"
+"```"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:76
+msgid ""
+"Like binary, we will use a prefix to denote hexadecimal, namely `$`.\n"
+"So, 42 = $2A, and 1024 = $400.\n"
+"This is *much* more compact than binary, and slightly more than decimal, "
+"too; but what makes hexadecimal very interesting is that one nibble "
+"corresponds *exactly* to 4 bits!"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:80
+msgid ""
+" Nibble | Bits\n"
+":------:|:----:\n"
+" $0 | %0000\n"
+" $1 | %0001\n"
+" $2 | %0010\n"
+" $3 | %0011\n"
+" $4 | %0100\n"
+" $5 | %0101\n"
+" $6 | %0110\n"
+" $7 | %0111\n"
+" $8 | %1000\n"
+" $9 | %1001\n"
+" $A | %1010\n"
+" $B | %1011\n"
+" $C | %1100\n"
+" $D | %1101\n"
+" $E | %1110\n"
+" $F | %1111"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:99
+msgid ""
+"This makes it very easy to convert between binary and hexadecimal, while "
+"retaining a compact enough notation.\n"
+"Thus, hexadecimal is used a lot more than binary.\n"
+"And, don't worry, decimal can still be used 😜"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:103
+msgid ""
+"(Side note: one could point that octal, i.e. base 8, would also work for "
+"this; however, we will primarily deal with units of 8 bits, for which "
+"hexadecimal works much better than octal. RGBDS supports octal via the `&` "
+"prefix, but I have yet to see it used.)"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:105
+msgid "::: tip:💡"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:107
+msgid ""
+"If you're having trouble converting between decimal and binary/hexadecimal, "
+"check if your favorite calculator program doesn't have a \"programmer\" "
+"mode, or a way to convert between bases."
+msgstr ""
+
+#: src/part1/bin_and_hex.md:111
+msgid "## Summary"
+msgstr ""
+
+#: src/part1/bin_and_hex.md:113
+msgid ""
+"- In RGBDS assembly, the hexadecimal prefix is `$`, and the binary prefix is "
+"`%`.\n"
+"- Hexadecimal can be used as a \"compact binary\" notation.\n"
+"- Using binary or hexadecimal is useful when individual bits matter; "
+"otherwise, decimal works just as well.\n"
+"- For when numbers get a bit too long, RGBASM allows underscores between "
+"digits (`123_465`, `%10_1010`, `$DE_AD_BE_EF`, etc.)"
+msgstr ""
+
+#: src/part1/registers.md:1
+msgid "# Registers"
+msgstr ""
+
+#: src/part1/registers.md:3
+msgid ""
+"Alright!\n"
+"Now that we know what bits are, let's talk about how they're used.\n"
+"Don't worry, this is mostly prep work for the next section, where we will—"
+"finally!—look at the code 👀"
+msgstr ""
+
+#: src/part1/registers.md:7
+msgid ""
+"First, if you opened BGB, you have been greeted with just the Game Boy "
+"screen.\n"
+"So, it's time we pop the debugger open!\n"
+"Right-click the screen, select \"Other\", and click \"Debugger\".\n"
+"Oh, and while we're at this, we might as well increase the screen size a "
+"little."
+msgstr ""
+
+#: src/part1/registers.md:12
+msgid ""
+""
+msgstr ""
+
+#: src/part1/registers.md:19
+msgid ""
+"The debugger may look intimidating at first, but don't worry, soon we'll be "
+"very familiar with it!\n"
+"For now, let's focus on this small box near the top-right, the *register "
+"viewer*."
+msgstr ""
+
+#: src/part1/registers.md:22
+msgid ""
+""
+msgstr ""
+
+#: src/part1/registers.md:26
+msgid ""
+"The register viewer shows both *CPU registers* and some *hardware "
+"registers*.\n"
+"This lesson will only deal with CPU registers, so that's why we will be "
+"ignoring some of these entries here."
+msgstr ""
+
+#: src/part1/registers.md:31
+msgid ""
+"What are CPU registers?\n"
+"Well, imagine you're preparing a cake.\n"
+"You will be following a recipe, whose instructions may be \"melt 125g of "
+"chocolate and 125g of butter, blend with 2 eggs\" and so on.\n"
+"You will fetch some ingredients from the fridge as needed, but you don't "
+"cook inside the fridge; for that, you have a small workspace."
+msgstr ""
+
+#: src/part1/registers.md:36
+msgid ""
+"Registers are pretty much the CPU's workspace.\n"
+"They are small, tiny chunks of memory embedded directly in the CPU (only 10 "
+"bytes for the Game Boy's CPU, and even modern CPUs have less than a kilobyte "
+"if you don't count SIMD registers).\n"
+"Operations are not performed directly on data stored in memory, which would "
+"be equivalent to breaking eggs directly inside our fridge, but they are "
+"performed on registers."
+msgstr ""
+
+#: src/part1/registers.md:42
+msgid ""
+"There are exceptions to this rule, like many other \"rules\" I will give in "
+"this tutorial; I will paper over them to keep the mental complexity "
+"reasonable, but don't treat my word as gospel either."
+msgstr ""
+
+#: src/part1/registers.md:46
+msgid "## General-purpose registers"
+msgstr ""
+
+#: src/part1/registers.md:48
+msgid ""
+"CPU registers can be placed into two categories: *general-purpose* and "
+"*special-purpose*.\n"
+"A \"general-purpose\" register (GPR for short) can be used for storing arbitrary integer "
+"numbers.\n"
+"Some GPRs are special nonetheless, as we will see later; but the distinction "
+"is \"can I store arbitrary integers in it?\"."
+msgstr ""
+
+#: src/part1/registers.md:52
+msgid ""
+"I won't introduce special-purpose registers quite yet, as their purpose "
+"wouldn't make sense yet.\n"
+"Rather, they will be discussed as the relevant concepts are introduced."
+msgstr ""
+
+#: src/part1/registers.md:55
+msgid ""
+"The Game Boy CPU has seven 8-bit GPRs: `a`, `b`, `c`, `d`, `e`, `h`, and "
+"`l`.\n"
+"\"8-bit\" means that, well, they store 8 bits.\n"
+"Thus, they can store integers from 0 to 255 (%1111_1111 aka $FF)."
+msgstr ""
+
+#: src/part1/registers.md:59
+msgid ""
+"`a` is the *accumulator*, and we will see later that it can be used in "
+"special ways."
+msgstr ""
+
+#: src/part1/registers.md:61
+msgid ""
+"A special feature is that these registers, besides `a`, are *paired up*, and "
+"the pairs can be treated as the 16-bit registers `bc`, `de`, and `hl`.\n"
+"The pairs are *not* separate from the individual registers; for example, if "
+"`d` contains 192 ($C0) and `e` contains 222 ($DE), then `de` contains 49374 "
+"($C0DE) = 192 × 256 + 222.\n"
+"The other pairs work similarly."
+msgstr ""
+
+#: src/part1/registers.md:65
+msgid ""
+"Modifying `de` actually modifies both `d` and `e` at the same time, and "
+"modifying either individually also affects the pair.\n"
+"How do we modify registers?\n"
+"Let's see how, with our first assembly instructions!"
+msgstr ""
+
+#: src/part1/assembly.md:1
+msgid "# Assembly basics"
+msgstr ""
+
+#: src/part1/assembly.md:3
+msgid ""
+"Alright, now that we know what the tools *do*, let's see what language "
+"RGBASM speaks.\n"
+"I will take a short slice of the beginning of `hello-world.asm`, so that we "
+"agree on the line numbers, and you can get some syntax highlighting even if "
+"your editor doesn't support it."
+msgstr ""
+
+#: src/part1/assembly.md:6
+msgid ""
+"```rgbasm,linenos,start=1\n"
+"INCLUDE \"hardware.inc\""
+msgstr ""
+
+#: src/part1/assembly.md:9
+msgid "SECTION \"Header\", ROM0[$100]"
+msgstr ""
+
+#: src/part1/assembly.md:11 src/part1/header.md:147
+#: src/part2/getting-started.md:38
+msgid "\tjp EntryPoint"
+msgstr ""
+
+#: src/part1/assembly.md:13
+msgid "\tds $150 - @, 0 ; Make room for the header"
+msgstr ""
+
+#: src/part1/assembly.md:15
+msgid ""
+"EntryPoint:\n"
+"\t; Shut down audio circuitry\n"
+"\tld a, 0\n"
+"\tld [rNR52], a\n"
+"```"
+msgstr ""
+
+#: src/part1/assembly.md:21
+msgid ""
+"Let's analyze it.\n"
+"Note that I will be ignoring a *lot* of RGBASM's functionality; if you're "
+"curious to know more, you should wait until parts II and III, or [read the "
+"docs](https://rgbds.gbdev.io/docs)."
+msgstr ""
+
+#: src/part1/assembly.md:24
+msgid "## Comments"
+msgstr ""
+
+#: src/part1/assembly.md:26
+msgid ""
+"We'll start with line 10, which should appear gray above.\n"
+"Semicolons `;` denote *comments*.\n"
+"Everything from a semicolon to the end of the line is *ignored* by RGBASM.\n"
+"As you can see on line 7, comments need not be on an otherwise empty line."
+msgstr ""
+
+#: src/part1/assembly.md:31
+msgid ""
+"Comments are a staple of every good programming language; they are useful to "
+"give context as to what code is doing.\n"
+"They're the difference between \"Pre-heat the oven at 180 °C\" and \"Pre-"
+"heat the oven at 180 °C, any higher and the cake would burn\", basically.\n"
+"In any language, good comments are very useful; in assembly, they play an "
+"even more important role, as many common semantic facilities are not "
+"available."
+msgstr ""
+
+#: src/part1/assembly.md:35
+msgid "## Instructions"
+msgstr ""
+
+#: src/part1/assembly.md:37
+msgid ""
+"Assembly is a very line-based language.\n"
+"Each line can contain one of two things:\n"
+"- a *directive*, which instructs RGBASM to do something, or\n"
+"- an *instruction*[^instr_directive], which is written directly into the ROM."
+msgstr ""
+
+#: src/part1/assembly.md:42
+msgid ""
+"We will talk about directives later, for now let's focus on instructions: "
+"for example, in the snippet above, we will ignore lines 1 (`INCLUDE`), 7 "
+"(`ds`), and 3 (`SECTION`)."
+msgstr ""
+
+#: src/part1/assembly.md:44
+msgid ""
+"To continue the cake-baking analogy even further, instructions are like "
+"steps in a recipe.\n"
+"The console's processor (CPU) "
+"executes instructions one at a time, and that... eventually does something!\n"
+"Like baking a cake, drawing a \"Hello World\" image, or displaying a Game "
+"Boy programming tutorial!\n"
+"\\*wink\\* \\*wink\\*"
+msgstr ""
+
+#: src/part1/assembly.md:49
+msgid ""
+"Instructions have a *mnemonic*, which is a name they are given, and "
+"*operands*, which indicate what they should act upon.\n"
+"For example, in \"melt the chocolate and butter in a saucepan\", *the whole "
+"sentence* would be the instruction, *the verb* \"melt\" would be the "
+"mnemonic, and \"chocolate\", \"butter\", and \"saucepan\" the operands, i.e. "
+"some kind of parameters to the operation."
+msgstr ""
+
+#: src/part1/assembly.md:52
+msgid ""
+"Let's discuss the most fundamental instruction, **`ld`**.\n"
+"`ld` stands for \"LoaD\", and its purpose is simply to copy data from its "
+"right operand ([\"RHS\"](https://en."
+"wikipedia.org/wiki/Sides_of_an_equation)) into its left operand ([\"LHS\"](https://en.wikipedia.org/wiki/"
+"Sides_of_an_equation)).\n"
+"For example, take line 11's `ld a, 0`: it copies (\"loads\") the value 0 "
+"into the 8-bit register `a`[^ld_imm_from].\n"
+"If you look further in the file, line 33 has `ld a, b`, which causes the "
+"value in register `b` to be copied into register `a`."
+msgstr ""
+
+#: src/part1/assembly.md:57
+msgid ""
+"Instruction | Mnemonic | Effect\n"
+"------------|----------|----------------------\n"
+"Load | `ld` | Copies values around"
+msgstr ""
+
+#: src/part1/assembly.md:63
+msgid ""
+"Due to CPU limitations, not all operand combinations are valid for `ld` and "
+"many other instructions; we will talk about this when writing our own code "
+"later."
+msgstr ""
+
+#: src/part1/assembly.md:67 src/part1/jumps.md:34 src/part1/tiles.md:36
+msgid "::: tip:🤔"
+msgstr ""
+
+#: src/part1/assembly.md:69
+msgid ""
+"RGBDS has an [instruction reference](https://rgbds.gbdev.io/docs/gbz80.7) "
+"worth bookmarking, and you can also consult it locally with `man 7 gbz80` if "
+"RGBDS is installed on your machine (except Windows...).\n"
+"The descriptions there are more succinct, since they're intended as "
+"reminders, not as tutorials."
+msgstr ""
+
+#: src/part1/assembly.md:74
+msgid "## Directives"
+msgstr ""
+
+#: src/part1/assembly.md:76
+msgid ""
+"In a way, instructions are destined to the console's CPU, and comments are "
+"destined to the programmer.\n"
+"But some lines are neither, and are instead sort of metadata destined to "
+"RGBDS itself.\n"
+"Those are called *directives*, and our Hello World actually contains three "
+"of those."
+msgstr ""
+
+#: src/part1/assembly.md:80
+msgid "### Including other files"
+msgstr ""
+
+#: src/part1/assembly.md:82
+msgid ""
+"```rgbasm,linenos\n"
+"INCLUDE \"hardware.inc\"\n"
+"```"
+msgstr ""
+
+#: src/part1/assembly.md:86
+msgid ""
+"Line 1 *includes* `hardware.inc`[^hw_inc_directives].\n"
+"Including a file has the same effect as if you copy-pasted it, but without "
+"having to actually do that."
+msgstr ""
+
+#: src/part1/assembly.md:89
+msgid ""
+"It allows sharing code across files easily: for example, if two files `a."
+"asm` and `b.asm` were to include `hardware.inc`, you would only need to "
+"modify `hardware.inc` once for the modifications to apply to both `a.asm` "
+"and `b.asm`.\n"
+"If you instead copy-pasted the contents manually, you would have to edit "
+"both copies in `a.asm` and `b.asm` to apply the changes, which is more "
+"tedious and error-prone."
+msgstr ""
+
+#: src/part1/assembly.md:92
+msgid ""
+"`hardware.inc` defines a bunch of constants related to interfacing with the "
+"hardware.\n"
+"Constants are basically names with a value attached, so when you write out "
+"their name, they are replaced with their value.\n"
+"This is useful because, for example, it is easier to remember the address of "
+"the **LCD** **C**ontrol register as `rLCDC` than `$FF40`."
+msgstr ""
+
+#: src/part1/assembly.md:96
+msgid "We will discuss constants in more detail in Part Ⅱ."
+msgstr ""
+
+#: src/part1/assembly.md:98
+msgid "### Sections"
+msgstr ""
+
+#: src/part1/assembly.md:100
+msgid ""
+"Let's first explain what a \"section\" is, then we will see what line 3 does."
+msgstr ""
+
+#: src/part1/assembly.md:102
+msgid ""
+"A section represents a contiguous range of memory, and by default, ends up "
+"*somewhere* not known in advance.\n"
+"If you want to see where all the sections end up, you can ask RGBLINK to "
+"generate a \"map file\" with the `-m` flag:"
+msgstr ""
+
+#: src/part1/assembly.md:105
+msgid ""
+"```console\n"
+"$ rgblink hello-world.o -m hello-world.map\n"
+"```"
+msgstr ""
+
+#: src/part1/assembly.md:109
+msgid ""
+"...and we can see, for example, where the `\"Tilemap\"` section ended up:"
+msgstr ""
+
+#: src/part1/assembly.md:111
+msgid ""
+"```\n"
+" SECTION: $05a6-$07e5 ($0240 bytes) [\"Tilemap\"]\n"
+"```"
+msgstr ""
+
+#: src/part1/assembly.md:115
+msgid ""
+"Sections cannot be split by RGBDS, which is useful e.g. for code, since the "
+"processor executes instructions one right after the other (except jumps, as "
+"we will see later).\n"
+"There is a balance to be struck between too many and not enough sections, "
+"but it typically doesn't matter much until banking is introduced into the "
+"picture—and it won't be until much, much later."
+msgstr ""
+
+#: src/part1/assembly.md:118
+msgid ""
+"So, for now, let's just assume that one section should contain things that "
+"\"go together\" topically, and let's examine one of ours."
+msgstr ""
+
+#: src/part1/assembly.md:120
+msgid ""
+"```rgbasm,linenos,start=3\n"
+"SECTION \"Header\", ROM0[$100]\n"
+"```"
+msgstr ""
+
+#: src/part1/assembly.md:124
+msgid ""
+"So!\n"
+"What's happening here?\n"
+"Well, we are simply declaring a new section; all instructions and data after "
+"this line and until the next `SECTION` one will be placed in this newly-"
+"created section.\n"
+"Before the first `SECTION` directive, there is no \"active\" section, and "
+"thus generating code or data will be met with a `Cannot output data outside "
+"of a SECTION` error."
+msgstr ""
+
+#: src/part1/assembly.md:129
+msgid ""
+"The new section's name is \"`Header`\".\n"
+"Section names can contain any characters (and even be empty, if you want), "
+"and must be unique[^sect_name].\n"
+"The `ROM0` keyword indicates which \"memory type\" the section belongs to "
+"([here is a list](https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.5#SECTIONS)).\n"
+"We will discuss them in Part Ⅱ."
+msgstr ""
+
+#: src/part1/assembly.md:134
+msgid ""
+"The `[$100]` part is more interesting, in that it is unique to this "
+"section.\n"
+"See, I said above that:"
+msgstr ""
+
+#: src/part1/assembly.md:137
+msgid ""
+"> a section \\[...\\] by default, ends up *somewhere* not known in advance."
+msgstr ""
+
+#: src/part1/assembly.md:139
+msgid ""
+"However, some memory locations are special, and so sometimes we need a "
+"specific section to span a specific range of memory.\n"
+"To enable this, RGBASM provides the `[addr]` syntax, which *forces* the "
+"section's starting address to be `addr`."
+msgstr ""
+
+#: src/part1/assembly.md:142
+msgid ""
+"In this case, the memory range $100–$14F is special, as it is the *ROM's "
+"header*.\n"
+"We will discuss the header in a couple lessons, but for now, just know that "
+"we need not to put any of our code or data in that space.\n"
+"How do we do that?\n"
+"Well, first, we begin a section at address $100, and then we need to reserve "
+"some space."
+msgstr ""
+
+#: src/part1/assembly.md:147
+msgid "### Reserving space"
+msgstr ""
+
+#: src/part1/assembly.md:149
+msgid ""
+"```rgbasm,linenos,start=5\n"
+"\tjp EntryPoint"
+msgstr ""
+
+#: src/part1/assembly.md:152 src/part1/header.md:149
+#: src/part2/getting-started.md:40
+msgid ""
+"\tds $150 - @, 0 ; Make room for the header\n"
+"```"
+msgstr ""
+
+#: src/part1/assembly.md:155
+msgid ""
+"Line 7 claims to \"Make room for the header\", which I briefly mentioned "
+"just above.\n"
+"For now, let's focus on what `ds` actually does."
+msgstr ""
+
+#: src/part1/assembly.md:158
+msgid ""
+"`ds` is used for *statically* allocating memory.\n"
+"It simply reserves some amount of bytes, which are set to a given value.\n"
+"The first argument to `ds`, here `$150 - @`, is *how many bytes to "
+"reserve*.\n"
+"The second (optional) argument, here `0`, is *what value to set each "
+"reserved byte to*[^ds_pattern]."
+msgstr ""
+
+#: src/part1/assembly.md:163
+msgid "We will see why these bytes must be reserved in a couple of lessons."
+msgstr ""
+
+#: src/part1/assembly.md:165
+msgid ""
+"It is worth mentioning that this first argument here is an *expression*.\n"
+"RGBDS (thankfully!) supports arbitrary expressions essentially anywhere.\n"
+"This expression is a simple subtraction: $150 minus `@`, which is a special "
+"symbol that stands for \"the current memory address\"."
+msgstr ""
+
+#: src/part1/assembly.md:171
+msgid ""
+"A symbol is essentially \"a name attached to a value\", usually a number.\n"
+"We will explore the different types of symbols throughout the tutorial, "
+"starting with labels in the next section."
+msgstr ""
+
+#: src/part1/assembly.md:174
+msgid ""
+"A numerical symbol used in an expression evaluates to its value, which must "
+"be known when compiling the ROM—in particular, it can't depend on any "
+"register's contents."
+msgstr ""
+
+#: src/part1/assembly.md:178
+msgid ""
+"Oh, but you may be wondering what the \"memory addresses\" I keep mentioning "
+"are.\n"
+"Let's see about those!"
+msgstr ""
+
+#: src/part1/assembly.md:183
+msgid ""
+"[^instr_directive]:\n"
+"Technically, instructions in RGBASM are implemented as directives, basically "
+"writing their encoded form to the ROM; but the distinction between the "
+"instructions in the source code and those in the final ROM is not worth "
+"bringing up right now."
+msgstr ""
+
+#: src/part1/assembly.md:186
+msgid ""
+"[^ld_imm_from]:\n"
+"The curious reader may ask where the value is copied *from*. The answer is "
+"simply that the \\\"immediate\\\" byte ($00 in this example) is stored in "
+"ROM just after the instruction's opcode byte, and it's what gets copied to "
+"`a`.\n"
+"We will come back to this when we talk about how instructions are encoded "
+"later on."
+msgstr ""
+
+#: src/part1/assembly.md:190
+msgid ""
+"[^hw_inc_directives]:\n"
+"`hardware.inc` itself contains more directives, in particular to define a "
+"lot of symbols.\n"
+"They will be touched upon much later, so we won't look into `hardware.inc` "
+"yet."
+msgstr ""
+
+#: src/part1/assembly.md:194
+msgid ""
+"[^sect_name]:\n"
+"Section names actually only need to be unique for \"plain\" sections, and "
+"function differently with \"unionized\" and \"fragment\" sections, which we "
+"will discuss much later."
+msgstr ""
+
+#: src/part1/assembly.md:197
+msgid ""
+"[^ds_pattern]:\n"
+"Actually, since RGBASM 0.5.0, `ds` can accept a *list* of bytes, and will "
+"repeat the pattern for as many bytes as specified.\n"
+"It just complicates the explanation slightly, so I omitted it for now.\n"
+"Also, if the argument is omitted, it defaults to what is specified using the "
+"`-p` option **to RGBASM**."
+msgstr ""
+
+#: src/part1/memory.md:1
+msgid "# Memory"
+msgstr ""
+
+#: src/part1/memory.md:3
+msgid "::: tip:🎉"
+msgstr ""
+
+#: src/part1/memory.md:5
+msgid ""
+"Congrats, you have just finished the hardest lessons of the tutorial!\n"
+"Since you have the basics, from now on, we'll be looking at more and more "
+"concrete code."
+msgstr ""
+
+#: src/part1/memory.md:10
+msgid ""
+"If we look at line 29, we see `ld a, [de]`.\n"
+"Given what we just learned, this copies a value into register `a`... but "
+"where from?\n"
+"What do these brackets mean?\n"
+"To answer that, we need to talk about *memory*."
+msgstr ""
+
+#: src/part1/memory.md:15
+msgid "## What's a memory?"
+msgstr ""
+
+#: src/part1/memory.md:17
+msgid ""
+"The purpose of memory is to store information.\n"
+"On a piece of paper or a whiteboard, you can write letters to store the "
+"grocery list, for example.\n"
+"But what can you store in a computer memory?\n"
+"The answer to that question is *current*[^memory_magnetic].\n"
+"Computer memory is made of little cells that can store current.\n"
+"But, as we saw in the lesson about binary, the presence or absence of "
+"current can be used to encode binary numbers!"
+msgstr ""
+
+#: src/part1/memory.md:24
+msgid ""
+"tl;dr: memory **stores numbers**.\n"
+"In fact, memory is a *long* array of numbers, stored in cells.\n"
+"To uniquely identify each cell, it's given a number (what else!) called its "
+"*address*.\n"
+"Like street numbers!\n"
+"The first cell has address 0, then address 1, 2, and so on.\n"
+"On the Game Boy, each cell contains *8 bits*, i.e. a *byte*."
+msgstr ""
+
+#: src/part1/memory.md:31
+msgid ""
+"How many cells are there?\n"
+"Well, this is actually a trick question..."
+msgstr ""
+
+#: src/part1/memory.md:34
+msgid "## The many types of memory"
+msgstr ""
+
+#: src/part1/memory.md:36
+msgid ""
+"There are several memory chips in the Game Boy, but we can put them into two "
+"categories: ROM and RAM[^rom_ram_and].\n"
+"ROM simply designates memory that cannot be written to[^rom_ro], and RAM "
+"memory that can be written to."
+msgstr ""
+
+#: src/part1/memory.md:39
+msgid ""
+"Due to how they work, the CPU, as well as the memory chips, can only use a "
+"single number for addresses.\n"
+"Let's go back to the \"street numbers\" analogy: each memory chip is a "
+"street, with its own set of numbers, but the CPU has no idea what a street "
+"is, it only deals with street numbers.\n"
+"To allow the CPU to talk to multiple chips, a sort of \"postal service\", "
+"the *chip selector*, is tasked with translating the CPU's street numbers "
+"into a street & street number."
+msgstr ""
+
+#: src/part1/memory.md:43
+msgid ""
+"For example, let's say a convention is established where addresses 0 through "
+"1999 go to chip A's addresses 0–1999, 2000–2999 to chip B's "
+"0–999, and 3000–3999 to chip C's 0–999.\n"
+"Then, if the CPU asks for the byte at address 2791, the chip selector will "
+"ask chip B for the byte at its *own* address 791, and forward the reply to "
+"the CPU."
+msgstr ""
+
+#: src/part1/memory.md:46
+msgid ""
+"Since addresses dealt with by the CPU do not directly correspond to the "
+"chips' addresses, we talk about *logical* addresses (here, the CPU's) versus "
+"*physical* addresses (here, the chips'), and the correspondence is called a "
+"*memory map*.\n"
+"Since we are programming the CPU, we will only be dealing with **logical** "
+"addresses, but it's crucial to keep in mind that different addresses may be "
+"backed by different memory chips, since each chip has unique characteristics."
+msgstr ""
+
+#: src/part1/memory.md:49
+msgid ""
+"This may sound complicated, so here is a summary:\n"
+"- Memory stores numbers, each 8-bit on the Game Boy.\n"
+"- Memory is accessed byte by byte, and the cell being accessed is determined "
+"by an *address*, which is just a number.\n"
+"- The CPU deals with all memory uniformly, but there are several memory "
+"chips each with their own characteristics."
+msgstr ""
+
+#: src/part1/memory.md:54
+msgid "### Game Boy memory map"
+msgstr ""
+
+#: src/part1/memory.md:56
+msgid ""
+"Let's answer the question that introduced this section: how many memory "
+"cells are there on the Game Boy?\n"
+"Well, now, we can reframe this question as \"how many logical addresses are "
+"there?\" or \"how many physical addresses are there in total?\"."
+msgstr ""
+
+#: src/part1/memory.md:59
+msgid ""
+"Logical addresses, which again are just numbers, are 16-bit on the Game "
+"Boy.\n"
+"Therefore, there are 2^16 = 65536 logical addresses, from $0000 to $FFFF.\n"
+"How many physical addresses, though?\n"
+"Well, here is a memory map [courtesy of Pan Docs](https://gbdev.io/pandocs/"
+"Memory_Map.html) (though I will simplify it a bit):"
+msgstr ""
+
+#: src/part1/memory.md:64
+msgid ""
+"Start | End | Name | Description\n"
+"------|-------|------|-------------------------------------------------------------------------\n"
+"$0000 | $7FFF | ROM | The game ROM, supplied by the cartridge.\n"
+"$8000 | $9FFF | VRAM | Video RAM, where graphics are stored and arranged.\n"
+"$A000 | $BFFF | SRAM | Save RAM, optionally supplied by the cartridge to "
+"save data to.\n"
+"$C000 | $DFFF | WRAM | Work RAM, general-purpose RAM for the game to store "
+"things in.\n"
+"$FE00 | $FE9F | OAM | Object Attribute Memory, where \"objects\" are "
+"stored.\n"
+"$FF00 | $FF7F | I/O | Neither ROM nor RAM, but this is where you control "
+"the console.\n"
+"$FF80 | $FFFE | HRAM | High RAM, a tiny bit of general-purpose RAM which can "
+"be accessed faster.\n"
+"$FFFF | $FFFF | IE | A lone I/O byte that's separated from the rest for some "
+"reason."
+msgstr ""
+
+#: src/part1/memory.md:75
+msgid ""
+"$8000 + $2000 + $2000 + $2000 + $A0 + $80 + $7F + 1 adds up to $E1A0, or "
+"57760 bytes of memory that can be *actually* accessed.\n"
+"The curious reader will naturally ask, \"What about the remaining 7776 "
+"bytes? What happens when accessing them?\"; the answer is: \"It depends, "
+"it's complicated; avoid accessing them\"."
+msgstr ""
+
+#: src/part1/memory.md:78
+msgid "## Labels"
+msgstr ""
+
+#: src/part1/memory.md:80
+msgid ""
+"Okay, memory addresses are nice, but you can't possibly expect me to keep "
+"track of all these addresses manually, right??\n"
+"Well, fear not, for we have labels!"
+msgstr ""
+
+#: src/part1/memory.md:83
+msgid ""
+"Labels are [symbols](https://rgbds.gbdev.io/docs/v0.5.1/rgbasm.5#SYMBOLS) "
+"which basically allow attaching a name to a byte of memory.\n"
+"A label is declared like at line 9 (`EntryPoint:`): at the beginning of the "
+"line, write the label's name, followed by a colon, and it will refer to the "
+"byte right after itself.\n"
+"So, for example, `EntryPoint` refers to the `ld a, 0` right below it (more "
+"accurately, the first byte of that instruction, but we will get there when "
+"we get there)."
+msgstr ""
+
+#: src/part1/memory.md:89
+msgid ""
+"If you peek inside `hardware.inc`, you will see that for example `rNR52` is "
+"not defined as a label.\n"
+"That's because they are *constants*, which we will touch on later; since "
+"they can be used mostly like labels, we will conflate the two for now."
+msgstr ""
+
+#: src/part1/memory.md:94
+msgid ""
+"Writing out a label's name is equivalent to writing the address of the byte "
+"it's referencing (with a few exceptions we will see in Part Ⅱ).\n"
+"For example, consider the `ld de, Tiles` at line 25.\n"
+"`Tiles` (line 64) is referring to the first byte of the tile data; if we "
+"assume that the tile data ends up being stored starting at $0193, then `ld "
+"de, Tiles` is equivalent to `ld de, $0193`!"
+msgstr ""
+
+#: src/part1/memory.md:98
+msgid "## What's with the brackets?"
+msgstr ""
+
+#: src/part1/memory.md:100
+msgid ""
+"Right, we came into this because we wanted to know what the brackets in `ld "
+"a, [de]` mean.\n"
+"Well, they can basically be read as \"at address...\".\n"
+"For example, `ld a, b` can be read as \"copy into `a` the value stored in "
+"`b`\"; `ld a, [$5414]` would be read as \"copy into `a` the value stored at "
+"address $5414\", and `ld a, [de]` would be read as \"copy into `a` the value "
+"stored at address `de`\".\n"
+"Wait, what does that mean?\n"
+"Well, if `de` contains the value $5414, then `ld a, [de]` will do the same "
+"thing as `ld a, [$5414]`."
+msgstr ""
+
+#: src/part1/memory.md:108
+msgid ""
+"If you're familiar with C, these brackets are basically how the dereference "
+"operator is implemented."
+msgstr ""
+
+#: src/part1/memory.md:112
+msgid "### `hli`"
+msgstr ""
+
+#: src/part1/memory.md:114
+msgid ""
+"An astute reader will have noticed the `ld [hli], a` just below the `ld a, "
+"[de]` we have just studied.\n"
+"`[de]` makes sense because it's one of the register pairs we saw a couple "
+"lessons ago, but `[hli]`?\n"
+"It's actually a special notation, which can also be written as `[hl+]`.\n"
+"It functions as `[hl]`, but `hl` is *incremented* just after memory is "
+"accessed.\n"
+"`[hld]`/`[hl-]` is the mirror of this one, *decrementing* `hl` instead of "
+"incrementing it."
+msgstr ""
+
+#: src/part1/memory.md:120
+msgid "## An example"
+msgstr ""
+
+#: src/part1/memory.md:122
+msgid "So, if we look at the first two instructions of `CopyTiles`:"
+msgstr ""
+
+#: src/part1/memory.md:124
+msgid ""
+"```rgbasm,linenos,start=29\n"
+"\tld a, [de]\n"
+"\tld [hli], a\n"
+"```"
+msgstr ""
+
+#: src/part1/memory.md:129
+msgid ""
+"...we can see that we're copying the byte in memory *pointed to* by `de` "
+"(that is, whose address is contained in `de`) into the byte pointed to by "
+"`hl`.\n"
+"Here, `a` serves as temporary storage, since the CPU is unable to perform "
+"`ld [hl], [de]` directly."
+msgstr ""
+
+#: src/part1/memory.md:132
+msgid ""
+"While we're at this, let's examine the rest of `.copyTiles` in the following "
+"lessons!"
+msgstr ""
+
+#: src/part1/memory.md:136
+msgid ""
+"[^memory_magnetic]:\n"
+"Actually, this depends a lot on the type of memory.\n"
+"A lot of memory nowadays uses magnetic storage, but to keep the explanation "
+"simple, and to parallel the explanation of binary given earlier, let's "
+"assume that current is being used."
+msgstr ""
+
+#: src/part1/memory.md:140
+msgid ""
+"[^rom_ram_and]:\n"
+"There are other types of memory, such as flash memory or EEPROM, but only "
+"Flash has been used on the Game Boy, and for only a handful of games; so we "
+"can mostly forget about them."
+msgstr ""
+
+#: src/part1/memory.md:143
+msgid ""
+"[^rom_ro]:\n"
+"No, really!\n"
+"Mask ROM is created by literally punching holes into a layer of silicon "
+"using acid, and e.g. the console's boot ROM is made of hard-wired transitors "
+"within the CPU die.\n"
+"Good luck writing to that!\n"
+" \n"
+"\"ROM\" is sometimes (mis)used to refer to \"persistent memory\" chips, such "
+"as flash memory, whose write functionality was disabled.\n"
+"Most bootleg / \"repro\" Game Boy cartridges you can find nowadays actually "
+"contain flash; this is why you can reflash them using specialized hardware, "
+"but original cartridges cannot be."
+msgstr ""
+
+#: src/part1/header.md:1
+msgid "# Header"
+msgstr ""
+
+#: src/part1/header.md:3
+msgid "Let's go back to a certain line near the top of `hello-world.asm`."
+msgstr ""
+
+#: src/part1/header.md:5 src/part1/header.md:75
+msgid ""
+"```rgbasm,linenos,start=7\n"
+"\tds $150 - @, 0 ; Make room for the header\n"
+"```"
+msgstr ""
+
+#: src/part1/header.md:9
+msgid ""
+"What is this mysterious header, why are we making room for it, and more "
+"questions answered in this lesson!"
+msgstr ""
+
+#: src/part1/header.md:11
+msgid "## What is the header?"
+msgstr ""
+
+#: src/part1/header.md:13
+msgid ""
+"First order of business is explaining what the header *is*.\n"
+"It's the region of memory from $0104 to $014F (inclusive).\n"
+"It contains metadata about the ROM, such as its title, Game Boy Color "
+"compatibility, size,\n"
+"two checksums, and interestingly, the Nintendo logo that is displayed during "
+"the power-on animation."
+msgstr ""
+
+#: src/part1/header.md:20
+msgid ""
+"You can find this information and more [in the Pan Docs](https://gbdev.io/"
+"pandocs/The_Cartridge_Header)."
+msgstr ""
+
+#: src/part1/header.md:24
+msgid ""
+"Interestingly, most of the information in the header does not matter on real "
+"hardware (the ROM's size is determined only by the capacity of the ROM chip "
+"in the cartridge, not the header byte).\n"
+"In fact, some prototype ROMs actually have incorrect header info!"
+msgstr ""
+
+#: src/part1/header.md:27
+msgid ""
+"Most of the header was only used by Nintendo's manufacturing department to "
+"know what components to put in the cartridge when publishing a ROM.\n"
+"Thus, only ROMs sent to Nintendo had to have a fully correct header; ROMs "
+"used for internal testing only needed to pass the boot ROM's checks, "
+"explained further below."
+msgstr ""
+
+#: src/part1/header.md:30
+msgid ""
+"However, in our \"modern\" day and age, the header actually matters a lot.\n"
+"Emulators (including hardware emulators such as flashcarts) must emulate the "
+"hardware present in the cartridge.\n"
+"The header being the only source of information about what hardware the "
+"ROM's cartridge should contain, they rely on some of the values in the "
+"header."
+msgstr ""
+
+#: src/part1/header.md:34
+msgid "## Boot ROM"
+msgstr ""
+
+#: src/part1/header.md:36
+msgid "The header is intimately tied to what is called the **boot ROM**."
+msgstr ""
+
+#: src/part1/header.md:38
+msgid ""
+"The most observant and/or nostalgic of you may have noticed the lack of the "
+"boot-up animation and the Game Boy's signature \"ba-ding!\" in BGB.\n"
+"When the console powers up, the CPU does not begin executing instructions at "
+"address $0100 (where our ROM's entry point is), but at $0000."
+msgstr ""
+
+#: src/part1/header.md:41
+msgid ""
+"However, at that time, a small program called the *boot ROM*, burned within "
+"the CPU's silicon, is \"overlaid\" on top of our ROM!\n"
+"The boot ROM is responsible for the startup animation, but it also checks "
+"the ROM's header!\n"
+"Specifically, it verifies that the Nintendo logo and header checksums are "
+"correct; if either check fails, the boot ROM intentionally *locks up*, and "
+"our game never gets to run :("
+msgstr ""
+
+#: src/part1/header.md:45
+msgid "::: tip For the curious"
+msgstr ""
+
+#: src/part1/header.md:47
+msgid ""
+"You can find a more detailed description of what the boot ROM does [in the "
+"Pan Docs](https://gbdev.io/pandocs/Power_Up_Sequence), as well as an "
+"explanation of the logo check.\n"
+"Beware that it is quite advanced, though."
+msgstr ""
+
+#: src/part1/header.md:50
+msgid ""
+"If you want to enable the boot ROMs in BGB, you must obtain a copy of the "
+"boot ROM(s), whose SHA256 checksums can be found [in their disassembly]"
+"(https://github.com/ISSOtm/gb-bootroms/blob/master/sha256sums.txt) for "
+"verification.\n"
+"If you wish, you can also compile [SameBoy's boot ROMs](https://github.com/"
+"LIJI32/SameBoy#compilation) and use those instead, as a free-software "
+"substitute."
+msgstr ""
+
+#: src/part1/header.md:53
+msgid ""
+"Then, in BGB's options, go to the `System` tab, set the paths to the boot "
+"ROMs you wish to use, tick `Enable bootroms`, select the appropriate system, "
+"and click `OK` or `Apply`.\n"
+"Now, just reset the emulator, and voilà!"
+msgstr ""
+
+#: src/part1/header.md:58
+msgid ""
+"A header is typically called \"valid\" if it would pass the boot ROM's "
+"checks, and \"invalid\" otherwise."
+msgstr ""
+
+#: src/part1/header.md:62
+msgid ""
+"RGBFIX is the third component of RGBDS, whose purpose is to write a ROM's "
+"header.\n"
+"It is separate from RGBLINK so that it can be used as a stand-alone tool.\n"
+"Its name comes from that RGBLINK typically does not produce a ROM with a "
+"valid header, so the ROM must be \"fixed\" before it's production-ready."
+msgstr ""
+
+#: src/part1/header.md:66
+msgid ""
+"RGBFIX has [a bunch of options](https://rgbds.gbdev.io/docs/rgbfix.1) to set "
+"various parts of the header; but the only two that we are using here are `-"
+"v`, which produces a **v**alid header (so, correct [Nintendo logo](https://"
+"gbdev.io/pandocs/The_Cartridge_Header.html#0104-0133---nintendo-logo) and "
+"[checksums](https://gbdev.io/pandocs/The_Cartridge_Header.html#014d---header-"
+"checksum)), and -p 0xFF, which **p**ads the ROM to the "
+"next valid size (using $FF as the filler byte), and writes the appropriate "
+"value to the [ROM size byte](https://gbdev.io/pandocs/The_Cartridge_Header."
+"html#0148---rom-size)."
+msgstr ""
+
+#: src/part1/header.md:68
+msgid ""
+"If you look at other projects, you may find RGBFIX invocations with more "
+"options, but these two should almost always be present."
+msgstr ""
+
+#: src/part1/header.md:70
+msgid "## So, what's the deal with that line?"
+msgstr ""
+
+#: src/part1/header.md:72
+msgid ""
+"Right!\n"
+"This line."
+msgstr ""
+
+#: src/part1/header.md:79
+msgid "Well, let's see what happens if we remove it (or comment it out)."
+msgstr ""
+
+#: src/part1/header.md:81
+msgid ""
+"```console\n"
+"$ rgbasm -L -o hello-world.o hello-world.asm\n"
+"$ rgblink -o hello-world.gb -n hello-world.sym hello-world.o\n"
+"```"
+msgstr ""
+
+#: src/part1/header.md:86
+msgid "(I am intentionally not running RGBFIX; we will see why in a minute.)"
+msgstr ""
+
+#: src/part1/header.md:88
+msgid "::: danger"
+msgstr ""
+
+#: src/part1/header.md:90
+msgid ""
+"Make sure the boot ROMs are not enabled for this!\n"
+"If they are, make sure to disable them (untick their box in the options, "
+"click `OK` or `Apply`, and reset the emulator)."
+msgstr ""
+
+#: src/part1/header.md:95
+msgid ""
+""
+msgstr ""
+
+#: src/part1/header.md:97
+msgid ""
+"As I explained, RGBFIX is responsible for writing the header, so we should "
+"use it to fix these warnings."
+msgstr ""
+
+#: src/part1/header.md:99
+msgid ""
+"```console\n"
+"$ rgbfix -v -p 0xFF hello-world.gb\n"
+"warning: Overwrote a non-zero byte in the Nintendo logo\n"
+"warning: Overwrote a non-zero byte in the header checksum\n"
+"```"
+msgstr ""
+
+#: src/part1/header.md:105
+msgid ""
+"*I'm sure these warnings are nothing to be worried about...*\n"
+"(Depending on your version of RGBDS, you may have gotten different warnings, "
+"or none at all.)"
+msgstr ""
+
+#: src/part1/header.md:108
+msgid "Let's run the ROM..."
+msgstr ""
+
+#: src/part1/header.md:110
+msgid ""
+""
+msgstr ""
+
+#: src/part1/header.md:112
+msgid "... dismiss this pesky warning, and..."
+msgstr ""
+
+#: src/part1/header.md:114
+msgid ""
+"\n"
+" \n"
+" \n"
+" When the debugger pops open on its own, and the title bar reads "
+"\"invalid opcode\", you might have screwed up somewhere.\n"
+" \n"
+""
+msgstr ""
+
+#: src/part1/header.md:121
+msgid ""
+msgstr ""
+
+#: src/part1/header.md:123
+msgid "Okay, so, what happened?"
+msgstr ""
+
+#: src/part1/header.md:125
+msgid ""
+"As we can see from the screenshot, PC is at $0105.\n"
+"What is it doing there?"
+msgstr ""
+
+#: src/part1/header.md:128
+msgid ""
+"...Oh, `EntryPoint` is at $0103.\n"
+"So the `jp` at $0100 went there, and started executing instructions (`3E CE` "
+"is the raw form of `ld a, $CE`), but then $ED does not encode any valid "
+"instruction, so the CPU locks up."
+msgstr ""
+
+#: src/part1/header.md:131
+msgid ""
+"But why is `EntryPoint` there?\n"
+"Well, as you may have figured out from the warnings RGBFIX printed, it "
+"*overwrites* the header area in the ROM.\n"
+"However, RGBLINK is **not** aware of the header (because RGBLINK is not only "
+"used to generate ROMs!), so you must explicitly reserve space for the header "
+"area."
+msgstr ""
+
+#: src/part1/header.md:135
+msgid "::: danger:🥴"
+msgstr ""
+
+#: src/part1/header.md:137
+msgid ""
+"Forgetting to reserve this space, and having a piece of code or data ending "
+"up there then overwritten, is a common beginner mistake that can be quite "
+"puzzling.\n"
+"Fortunately, RGBFIX since version 0.5.1 warns when it detects this mistake, "
+"as shown above."
+msgstr ""
+
+#: src/part1/header.md:142
+msgid "So, we prevent disaster like this:"
+msgstr ""
+
+#: src/part1/header.md:144 src/part2/getting-started.md:35
+msgid ""
+"```rgbasm,linenos,start=3\n"
+"SECTION \"Header\", ROM0[$100]"
+msgstr ""
+
+#: src/part1/header.md:152
+msgid ""
+"The directive `ds` stands for \"define space\", and allows filling a range "
+"of memory.\n"
+"This specific line fills all bytes from $103 to $14F (inclusive) with the "
+"value $00.\n"
+"Since different pieces of code and/or data cannot overlap, this ensures that "
+"the header's memory range can safely be overwritten by RGBFIX, and that "
+"nothing else accidentally gets steamrolled instead."
+msgstr ""
+
+#: src/part1/header.md:156
+msgid ""
+"It may not be obvious how this `ds` ends up filling that specific memory "
+"range.\n"
+"The 3-byte `jp` covers memory addresses $100, $101, and $102.\n"
+"(We start at $100 because that's where the `SECTION` is hardcoded to be.)\n"
+"When RGBASM processes the `ds` directive, `@` (which is a special symbol "
+"that evaluates to \"the current address\") thus has the value $103, so it "
+"fills `$150 - $103 = $4D` bytes with zeros, so $103, $104, ..., $14E, $14F."
+msgstr ""
+
+#: src/part1/header.md:161
+msgid "## Bonus: the infinite loop"
+msgstr ""
+
+#: src/part1/header.md:163
+msgid ""
+"(This is not really linked to the header, but I need to explain it "
+"somewhere, and here is as good a place as any.)"
+msgstr ""
+
+#: src/part1/header.md:165
+msgid ""
+"You may also be wondering what the point of the infinite loop at the end of "
+"the code is for."
+msgstr ""
+
+#: src/part1/header.md:167
+msgid ""
+"```rgbasm\n"
+"Done:\n"
+"\tjp Done\n"
+"```"
+msgstr ""
+
+#: src/part1/header.md:172
+msgid ""
+"Well, simply enough, the CPU never stops executing instructions; so when our "
+"little Hello World is done and there is nothing left to do, we must still "
+"give the CPU some busy-work: so we make it do nothing, forever."
+msgstr ""
+
+#: src/part1/header.md:174
+msgid ""
+"We cannot let the CPU just run off, as it would then start executing other "
+"parts of memory as code, possibly crashing.\n"
+"(See for yourself: remove or comment out these two lines, re-[compile the "
+"ROM](hello_world.md), and see what happens!)"
+msgstr ""
+
+#: src/part1/operations.md:1
+msgid "# Operations & flags"
+msgstr ""
+
+#: src/part1/operations.md:3
+msgid ""
+"Alright, we know how to pass values around, but just copying numbers is no "
+"fun; we want to modify them!"
+msgstr ""
+
+#: src/part1/operations.md:5
+msgid ""
+"The GB CPU does not provide every operation under the sun (for example, "
+"there is no multiplication instruction), but we can just program those "
+"ourselves with what we have.\n"
+"Let's talk about some of the operations that it *does* have; I will be "
+"omitting some not used in the Hello World for now."
+msgstr ""
+
+#: src/part1/operations.md:8
+msgid "## Arithmetic"
+msgstr ""
+
+#: src/part1/operations.md:10
+msgid ""
+"The simplest arithmetic instructions the CPU supports are `inc` and `dec`, "
+"which INCrement and DECrement their operand, respectively.\n"
+"(If you aren't sure, \"to increment\" means \"to add 1\", and \"to "
+"decrement\" means \"to subtract 1\".)\n"
+"So for example, the `dec bc` at line 32 of `hello-world.asm` simply "
+"subtracts 1 from `bc`."
+msgstr ""
+
+#: src/part1/operations.md:14
+msgid ""
+"Okay, cool!\n"
+"Can we go a bit faster, though?\n"
+"Sure we can, with `add` and `sub`!\n"
+"These respectively ADD and SUBtract arbitrary values (either a constant, or "
+"a register).\n"
+"Neither is used in the tutorial, but a sibling of `sub`'s is: have you "
+"noticed little `cp` over at line 17?\n"
+"`cp` allows ComParing values.\n"
+"It works the same as `sub`, but it discards the result instead of writing it "
+"back.\n"
+"\"Wait, so it does nothing?\" you may ask; well, it *does* update the "
+"**flags**."
+msgstr ""
+
+#: src/part1/operations.md:23
+msgid "## Flags"
+msgstr ""
+
+#: src/part1/operations.md:25
+msgid ""
+"The time has come to talk about the special-purpose register (remember "
+"those?) `f`, for, well, *flags*.\n"
+"The `f` register contains 4 bits, called \"flags\", which are updated "
+"depending on an operation's results.\n"
+"These 4 flags are:"
+msgstr ""
+
+#: src/part1/operations.md:29
+msgid ""
+"Name | Description\n"
+"-----|---------------------\n"
+" Z | Zero flag\n"
+" N | Addition/subtraction\n"
+" H | Half-carry\n"
+" C | Carry"
+msgstr ""
+
+#: src/part1/operations.md:36
+msgid ""
+"Yes, there is a flag called \"C\" and a register called \"c\", and **they "
+"are different, unrelated things**.\n"
+"This makes the syntax a bit confusing at the beginning, but they are always "
+"used in different contexts, so it's fine."
+msgstr ""
+
+#: src/part1/operations.md:39
+msgid ""
+"We will forget about N and H for now; let's focus on Z and C.\n"
+"Z is the simplest flag: it gets set when an operation's result is 0, and "
+"gets reset otherwise.\n"
+"C is set when an operation *overflows* or *underflows*."
+msgstr ""
+
+#: src/part1/operations.md:43
+msgid ""
+"What's an overflow?\n"
+"Let's take the simple instruction `add a, 42`.\n"
+"This simply adds 42 to the contents of register `a`, and writes the result "
+"back into `a`."
+msgstr ""
+
+#: src/part1/operations.md:47
+msgid ""
+"```rgbasm\n"
+" ld a, 200\n"
+" add a, 42\n"
+"```"
+msgstr ""
+
+#: src/part1/operations.md:52
+msgid ""
+"At the end of this snippet, `a` equals 200 + 42 = 242, great!\n"
+"But what if I write this instead?"
+msgstr ""
+
+#: src/part1/operations.md:55
+msgid ""
+"```rgbasm\n"
+" ld a, 220\n"
+" add a, 42\n"
+"```"
+msgstr ""
+
+#: src/part1/operations.md:60
+msgid ""
+"Well, one could think that `a` would be equal to 220 + 42 = 262, but that "
+"would be incorrect.\n"
+"Remember, `a` is an 8-bit register, *it can only store eight bits of "
+"information*!\n"
+"And if we were to write 262 in binary, we would get %100000110, which "
+"requires at least 9 bits...\n"
+"So what happens?\n"
+"Simply, that ninth bit is *lost*, and the value that we end up with is "
+"%00000110 = 6.\n"
+"This is called an *overflow*: after **adding**, we get a value **smaller** "
+"than what we started with."
+msgstr ""
+
+#: src/part1/operations.md:67
+msgid ""
+"We can also do the opposite with `sub`, and—for example—subtract "
+"42 from 6; as we know, for all `X` and `Y`, `X + Y - Y = X`, and we just saw "
+"that 220 + 42 = 6 (this is called *modulo 256 arithmetic*, by the way); so, "
+"6 - 42 = (220 + 42) - 42 = 220.\n"
+"This is called an *underflow*: after **subtracting**, we get a value "
+"**greater** than what we started with."
+msgstr ""
+
+#: src/part1/operations.md:70
+msgid ""
+"When an operation is performed, it sets the carry flag if an overflow or "
+"underflow occurred, and clears it otherwise.\n"
+"(We will see later that not all operations update the carry flag, though.)"
+msgstr ""
+
+#: src/part1/operations.md:73
+msgid "::: tip Summary"
+msgstr ""
+
+#: src/part1/operations.md:75
+msgid ""
+"- We can add and subtract numbers.\n"
+"- The Z flag lets us know if the result was 0.\n"
+"- However, registers can only store a limited range of integers.\n"
+"- Going outside this range is called an **overflow** or **underflow**, for "
+"addition and subtraction respectively.\n"
+"- The C flag lets us know if either occurred."
+msgstr ""
+
+#: src/part1/operations.md:83
+msgid "## Comparison"
+msgstr ""
+
+#: src/part1/operations.md:85
+msgid ""
+"Now, let's talk more about how `cp` is used to compare numbers.\n"
+"Here is a refresher: `cp` subtracts its operand from `a` and updates flags "
+"accordingly, but doesn't write the result back.\n"
+"We can use flags to check properties about the values being compared, and we "
+"will see in the next lesson how to use the flags."
+msgstr ""
+
+#: src/part1/operations.md:89
+msgid ""
+"The simplest interaction is with the Z flag.\n"
+"If it's set, we know that the subtraction yielded 0, i.e. `a - operand == "
+"0`; therefore, `a == operand`!\n"
+"If it's not set, well, then we know that `a != operand`."
+msgstr ""
+
+#: src/part1/operations.md:93
+msgid ""
+"Okay, checking for equality is nice, but we may also want to perform "
+"*comparisons*.\n"
+"Fret not, for the carry flag is here to do just that!\n"
+"See, when performing a subtraction, the carry flag gets set when the result "
+"goes below 0—but that's just a fancy way of saying \"becomes negative\"!"
+msgstr ""
+
+#: src/part1/operations.md:97
+msgid ""
+"So, when the carry flag gets set, we know that `a - operand < 0`, therefore "
+"that `a < operand`..!\n"
+"And, conversely, we know that if it's *not* set, `a >= operand`.\n"
+"Great!"
+msgstr ""
+
+#: src/part1/operations.md:101
+msgid "## Instruction summary"
+msgstr ""
+
+#: src/part1/operations.md:103
+msgid ""
+"Instruction | Mnemonic | Effect\n"
+"------------|----------|---------------------------------------------\n"
+"Add | `add` | Adds values to `a`\n"
+"Subtract | `sub` | Subtracts values from `a`\n"
+"Compare | `cp` | Compares values with what's contained in `a`"
+msgstr ""
+
+#: src/part1/jumps.md:1
+msgid "# Jumps"
+msgstr ""
+
+#: src/part1/jumps.md:5
+msgid ""
+"Once this lesson is done, we will be able to understand all of `CopyTiles`!"
+msgstr ""
+
+#: src/part1/jumps.md:9
+msgid ""
+"So far, all the code we have seen was linear: it executes top to bottom.\n"
+"But this doesn't scale: sometimes, we need to perform certain actions "
+"depending on the result of others (\"if the crêpes start sticking, grease "
+"the pan again\"), and sometimes, we need to perform actions repeatedly (\"If "
+"there is some batter left, repeat from step 5\")."
+msgstr ""
+
+#: src/part1/jumps.md:12
+msgid ""
+"Both of these imply reading the recipe non-linearly.\n"
+"In assembly, this is achieved using *jumps*."
+msgstr ""
+
+#: src/part1/jumps.md:15
+msgid ""
+"The CPU has a special-purpose register called \"PC\", for Program Counter.\n"
+"It contains the address of the instruction currently being "
+"executed[^pc_updates], like how you'd keep in mind the number of the recipe "
+"step you're currently doing.\n"
+"PC increases automatically as the CPU reads instructions, so \"by default\" "
+"they are read sequentially; however, jump instructions allow writing a "
+"different value to PC, effectively *jumping* to another piece of the "
+"program.\n"
+"Hence the name."
+msgstr ""
+
+#: src/part1/jumps.md:20
+msgid ""
+"Okay, so, let's talk about those jump instructions, shall we?\n"
+"There are four of them:"
+msgstr ""
+
+#: src/part1/jumps.md:23
+msgid ""
+"Instruction | Mnemonic | Effect\n"
+"--------------|----------|---------------------------------------------\n"
+"Jump | `jp` | Jump execution to a location\n"
+"Jump Relative | `jr` | Jump to a location close by\n"
+"Call | `call` | Call a subroutine\n"
+"Return | `ret` | Return from a subroutine"
+msgstr ""
+
+#: src/part1/jumps.md:30
+msgid ""
+"We will focus on `jp` for now.\n"
+"`jp`, such as the one line 5, simply sets PC to its argument, jumping "
+"execution there.\n"
+"In other words, after executing `jp EntryPoint` (line 5), the next "
+"instruction executed is the one below `EntryPoint` (line 16)."
+msgstr ""
+
+#: src/part1/jumps.md:36
+msgid ""
+"You may be wondering what is the point of that specific `jp`.\n"
+"Don't worry, we will see later why it's required."
+msgstr ""
+
+#: src/part1/jumps.md:41
+msgid "## Conditional jumps"
+msgstr ""
+
+#: src/part1/jumps.md:43
+msgid ""
+"Now to the *really* interesting part.\n"
+"Let's examine the loop responsible for copying tiles:"
+msgstr ""
+
+#: src/part1/jumps.md:46
+msgid ""
+"```rgbasm,linenos,start=24\n"
+"\t; Copy the tile data\n"
+"\tld de, Tiles\n"
+"\tld hl, $9000\n"
+"\tld bc, TilesEnd - Tiles\n"
+"CopyTiles:\n"
+"\tld a, [de]\n"
+"\tld [hli], a\n"
+"\tinc de\n"
+"\tdec bc\n"
+"\tld a, b\n"
+"\tor a, c\n"
+"\tjp nz, CopyTiles\n"
+"```"
+msgstr ""
+
+#: src/part1/jumps.md:63
+msgid ""
+"Don't worry if you don't quite get all the following, as we'll see it live "
+"in action in the next lesson.\n"
+"If you're having trouble, try going to the next lesson, watch the code "
+"execute step by step; then, coming back here, it should make more sense."
+msgstr ""
+
+#: src/part1/jumps.md:68
+msgid ""
+"First, we copy `Tiles`, the address of the first byte of tile data, into "
+"`de`.\n"
+"Then, we set `hl` to $9000, which is the address where we will start copying "
+"the tile data to.\n"
+"`ld bc, TilesEnd - Tiles` sets `bc` to the length of the tile data: "
+"`TilesEnd` is the address of the first byte *after* the tile data, so "
+"subtracting `Tiles` to that yields the length."
+msgstr ""
+
+#: src/part1/jumps.md:72
+msgid "So, basically:"
+msgstr ""
+
+#: src/part1/jumps.md:74
+msgid ""
+"- `de` contains the address where data will be copied from;\n"
+"- `hl` contains the address where data will be copied to;\n"
+"- `bc` contains how many bytes we have to copy."
+msgstr ""
+
+#: src/part1/jumps.md:78
+msgid ""
+"Then we arrive at the main loop.\n"
+"We read one byte from the source (line 29), and write it to the destination "
+"(line 30).\n"
+"We increment the destination (via the implicit `inc hl` done by `ld [hli], "
+"a`) and source pointers (line 31), so the following loop iteration processes "
+"the next byte."
+msgstr ""
+
+#: src/part1/jumps.md:82
+msgid ""
+"Here's the interesting part: since we've just copied one byte, that means we "
+"have one less to go, so we `dec bc`.\n"
+"(We have seen `dec` two lessons ago; as a refresher, it simply decreases the "
+"value stored in `bc` by one.)\n"
+"Since `bc` contains the amount of bytes that still need to be copied, it's "
+"trivial to see that we should simply repeat the operation if `bc` != 0."
+msgstr ""
+
+#: src/part1/jumps.md:86
+msgid "::: danger:😓"
+msgstr ""
+
+#: src/part1/jumps.md:88
+msgid ""
+"`dec` usually updates flags, but unfortunately `dec bc` doesn't, so we must "
+"check if `bc` reached 0 manually."
+msgstr ""
+
+#: src/part1/jumps.md:92
+msgid ""
+"`ld a, b` and `or a, c` \"bitwise OR\" `b` and `c` together; it's enough to "
+"know for now that it leaves 0 in `a` if and only if `bc` == 0.\n"
+"And `or` updates the Z flag!\n"
+"So, after line 34, the Z flag is set if and only if `bc` == 0, that is, if "
+"we should exit the loop."
+msgstr ""
+
+#: src/part1/jumps.md:96
+msgid ""
+"And this is where conditional jumps come into the picture!\n"
+"See, it's possible to **conditionally** \"take\" a jump depending on the "
+"state of the flags."
+msgstr ""
+
+#: src/part1/jumps.md:99
+msgid "There are four \"conditions\":"
+msgstr ""
+
+#: src/part1/jumps.md:101
+msgid ""
+"Name | Mnemonic | Description\n"
+"---------|----------|----------------------------------------------------\n"
+"Zero | `z` | Z is set (last operation had a result of 0)\n"
+"Non-zero | `nz` | Z is not set (last operation had a non-zero result)\n"
+"Carry | `c` | C is set (last operation overflowed)\n"
+"No carry | `nc` | C is not set (last operation did not overflow)"
+msgstr ""
+
+#: src/part1/jumps.md:108
+msgid ""
+"Thus, `jp nz, CopyTiles` can be read as \"if the Z flag is not set, then "
+"jump to `CopyTiles`\".\n"
+"Since we're jumping *backwards*, we will repeat the instructions again: we "
+"have just created a **loop**!"
+msgstr ""
+
+#: src/part1/jumps.md:111
+msgid ""
+"Okay, we've been talking about the code a lot, and we have seen it run, but "
+"we haven't really seen *how* it runs.\n"
+"Let's watch the magic unfold in slow-motion in the next lesson!"
+msgstr ""
+
+#: src/part1/jumps.md:116
+msgid ""
+"[^pc_updates]:\n"
+"Not exactly; instructions may be several bytes long, and PC increments after "
+"reading each byte.\n"
+"Notably, this means that when an instruction finishes executing, PC is "
+"pointing to the following instruction.\n"
+"Still, it's pretty much \"where the CPU is currently reading from\", but "
+"it's better to keep it simple and avoid mentioning instruction encoding for "
+"now."
+msgstr ""
+
+#: src/part1/tracing.md:1
+msgid "# Tracing"
+msgstr ""
+
+#: src/part1/tracing.md:3
+msgid ""
+"Ever dreamed of being a wizard?\n"
+"Well, this won't give you magical powers, but let's see how emulators can be "
+"used to control time!"
+msgstr ""
+
+#: src/part1/tracing.md:6
+msgid ""
+"First, make sure to focus the debugger window.\n"
+"Let's first explain the debugger's layout:\n"
+"\n"
+"Top-left is the code viewer, bottom-left is the data viewer, top-right are "
+"some registers (as we saw in [the registers lesson](registers.html)), and "
+"bottom-right is the stack viewer.\n"
+"What's the stack?\n"
+"We will answer that question a bit later... in Part Ⅱ 😅"
+msgstr ""
+
+#: src/part1/tracing.md:13
+msgid "## Setup"
+msgstr ""
+
+#: src/part1/tracing.md:15
+msgid ""
+"For now, let's focus on the code viewer.\n"
+"If you focus the debugger while the emulator is running, it will pause, as "
+"indicated by the screen window's title changing to `bgb (debugging)`."
+msgstr ""
+
+#: src/part1/tracing.md:18
+msgid ""
+"Okay, but what's with this weird syntax?\n"
+"Well, there are several syntaxes for Game Boy assembly, and BGB doesn't use "
+"RGBDS' by default.\n"
+"We can fix that in the options, which we can access by either:\n"
+"- Right-clicking the screen and selecting \"Options\"\n"
+"- In the debugger, open the \"Window\" menu, and select \"Options\"\n"
+"- Press F11 while focusing either the screen or "
+"debugger"
+msgstr ""
+
+#: src/part1/tracing.md:25
+msgid ""
+"BGB has a *ton* of options, but don't worry, it's got good defaults, so we "
+"don't need to look at most of them for now.\n"
+"Select the \"Debug\" tab, and set \"Disasm syntax\" to \"rgbds\".\n"
+"Oh, and check the \"Local symbols\" box, too.\n"
+"Now, click \"OK\" to apply the options."
+msgstr ""
+
+#: src/part1/tracing.md:30
+msgid ""
+msgstr ""
+
+#: src/part1/tracing.md:32
+msgid "::: warning"
+msgstr ""
+
+#: src/part1/tracing.md:34
+msgid ""
+"You can also customize a lot of key bindings in the options, but I will "
+"stick to the default ones for this tutorial for the sake of simplicity."
+msgstr ""
+
+#: src/part1/tracing.md:38
+msgid ""
+"Alright, great!\n"
+"But where are the labels?\n"
+"Well, as we have seen a couple of lessons ago, labels are merely a "
+"convenience provided by RGBASM, but they are not part of the ROM itself.\n"
+"It is very much inconvenient to debug without them, and so sym files (for "
+"\"**sym**bols\") have been developed.\n"
+"Let's run RGBLINK to generate a sym file for our ROM:"
+msgstr ""
+
+#: src/part1/tracing.md:44
+msgid ""
+"```console\n"
+"$ rgblink -n hello-world.sym hello-world.o\n"
+"```"
+msgstr ""
+
+#: src/part1/tracing.md:48
+msgid "::: warning:‼️"
+msgstr ""
+
+#: src/part1/tracing.md:50
+msgid ""
+"The file names matter!\n"
+"When looking for a ROM's sym file, BGB takes the ROM's file name, strips the "
+"extension (here, `.gb`), replaces it with `.sym`, and looks for a file **in "
+"the same directory** with that name."
+msgstr ""
+
+#: src/part1/tracing.md:55
+msgid ""
+"Then, in the debugger, we can go in the \"File\" menu and select \"reload "
+"SYM file\"."
+msgstr ""
+
+#: src/part1/tracing.md:57
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tracing.md:59
+msgid "Much better!"
+msgstr ""
+
+#: src/part1/tracing.md:61
+msgid "::: tip:🔍"
+msgstr ""
+
+#: src/part1/tracing.md:63
+msgid ""
+"If a sym file is loaded, pressing Tab allows toggling "
+"whether labels are displayed or not."
+msgstr ""
+
+#: src/part1/tracing.md:67
+msgid "## Stepping"
+msgstr ""
+
+#: src/part1/tracing.md:69
+msgid ""
+"When pausing execution, the debugger will automatically focus on the "
+"instruction the CPU is about to execute, as indicated by the line "
+"highlighted in blue.\n"
+""
+msgstr ""
+
+#: src/part1/tracing.md:74
+msgid ""
+"The instruction highlighted in blue is always what the CPU is *about to "
+"execute*, not what it *just executed*. Keep this in mind."
+msgstr ""
+
+#: src/part1/tracing.md:78
+msgid ""
+"If we want to watch execution from the beginning, we need to reset the "
+"emulator.\n"
+"Go into the debugger's \"Run\" menu, and select \"Reset\", or tap your "
+"numpad's \\* key."
+msgstr ""
+
+#: src/part1/tracing.md:81
+msgid ""
+"The blue line should automatically move to address $0100[^boot_addr], and "
+"now we're ready to trace!\n"
+"All the commands for that are in the \"Run\" menu."
+msgstr ""
+
+#: src/part1/tracing.md:84
+msgid ""
+"- \"Run\" simply unpauses the emulator. Clicking on the screen also does the "
+"same.\n"
+"- \"Trace\" (more commonly known as \"Step Into\") and \"Step Over\" advance "
+"the emulator by one instruction.\n"
+"They only really differ on the `call` instruction and interrupts, neither of "
+"which we are using here, so we will use \"Trace\".\n"
+"- The other options are not relevant for now."
+msgstr ""
+
+#: src/part1/tracing.md:89
+msgid ""
+"We will have to \"Trace\" a bunch of times, so it's a good idea to use the "
+"key shortcut.\n"
+"If we press F7 once, the `jp EntryPoint` is executed.\n"
+"And if we press it a few more times, can see the instructions being "
+"executed, one by one!"
+msgstr ""
+
+#: src/part1/tracing.md:93
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tracing.md:100
+msgid ""
+"Now, you may notice the `WaitVBlank` loop runs a *lot* of times, but what we "
+"are interested in is the `CopyTiles` loop.\n"
+"We can easily skip over it in several ways; this time, we will use a "
+"*breakpoint*.\n"
+"We will place the breakpoint on the `ld de, Tiles` at `00:0162`; either "
+"double-click on that line, or select it and press F2.\n"
+"The line will turn red:"
+msgstr ""
+
+#: src/part1/tracing.md:105
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tracing.md:107
+msgid ""
+"Then you can resume execution either by clicking the screen or pressing "
+"F9, and BGB will automatically pause.\n"
+"Whenever BGB is running, and the (emulated) CPU is about to execute an "
+"instruction a breakpoint was placed on, it automatically pauses."
+msgstr ""
+
+#: src/part1/tracing.md:110
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tracing.md:112
+msgid ""
+"You can see where execution is being paused both from the green arrow and "
+"the value of PC."
+msgstr ""
+
+#: src/part1/tracing.md:114
+msgid ""
+"If we trace the next three instructions, we can see the three arguments to "
+"the `CopyTiles` loop getting loaded into registers."
+msgstr ""
+
+#: src/part1/tracing.md:116
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tracing.md:118
+msgid ""
+"For fun, let's watch the tiles as they're being copied.\n"
+"For that, obviously, we will use the data viewer, and position it at the "
+"destination.\n"
+"As we can see from the image above, that would be $9000!"
+msgstr ""
+
+#: src/part1/tracing.md:122
+msgid ""
+"Select the data viewer (either click somewhere in it, or use Ctrl"
+"kbd>+Tab to switch focus, as indicated by the grey bar on "
+"the left), and press Ctrl+G (for \"Goto\").\n"
+"In the popup, type the address you wish to go to, in our case `9000` (sans "
+"dollar sign!!)."
+msgstr ""
+
+#: src/part1/tracing.md:125
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tracing.md:132
+msgid "Awesome, right?"
+msgstr ""
+
+#: src/part1/tracing.md:134
+msgid "## What next?"
+msgstr ""
+
+#: src/part1/tracing.md:136
+msgid ""
+"Congrats, you have just learned how to use a debugger!\n"
+"We have only scratched the surface, though; we will use more of BGB's tools "
+"to illustrate the next parts.\n"
+"Don't worry, from here on, lessons will go with a lot more images—you've "
+"made it through the hardest part!"
+msgstr ""
+
+#: src/part1/tracing.md:142
+msgid ""
+"[^boot_addr]:\n"
+"Why does execution start at $0100?\n"
+"That's because it's where the [boot ROM](https://gbdev.io/pandocs/"
+"Power_Up_Sequence) hands off control to our game once it's done."
+msgstr ""
+
+#: src/part1/tiles.md:1
+msgid "# Tiles"
+msgstr ""
+
+#: src/part1/tiles.md:3
+msgid "::: tip:💭"
+msgstr ""
+
+#: src/part1/tiles.md:5
+msgid ""
+"\"Tiles\" were called differently in documentation of yore.\n"
+"They were usually called \"patterns\" or \"characters\", the latter giving "
+"birth to the \"CHR\" abbreviation which is sometimes used to refer to tiles."
+msgstr ""
+
+#: src/part1/tiles.md:8
+msgid ""
+"For example, on the NES, tile data is usually provided by the cartridge in "
+"either [CHR ROM or CHR RAM](http://wiki.nesdev.com/w/index.php/CHR_ROM_vs."
+"_CHR_RAM).\n"
+"The term \"CHR\" is typically not used on the Game Boy, though exchanges "
+"between communities cause terms to \"leak\", so some refer to the area of "
+"VRAM where tiles are stored as \"CHR RAM\" or \"CHR VRAM\", for example."
+msgstr ""
+
+#: src/part1/tiles.md:11
+msgid ""
+"As with all such jargon whose meaning may depend on who you are talking to, "
+"I will stick to \"tiles\" across this entire tutorial for consistency, being "
+"what is the most standard in the GB dev community now."
+msgstr ""
+
+#: src/part1/tiles.md:15
+msgid ""
+"Well, copying this data blindly is fine and dandy, but why exactly is the "
+"data \"graphics\"?"
+msgstr ""
+
+#: src/part1/tiles.md:17
+msgid ""
+"\n"
+" \n"
+" Ah, yes, pixels.\n"
+""
+msgstr ""
+
+#: src/part1/tiles.md:22
+msgid "Let's see about that!"
+msgstr ""
+
+#: src/part1/tiles.md:24
+msgid "## Helpful hand"
+msgstr ""
+
+#: src/part1/tiles.md:26
+msgid ""
+"Now, figuring out the format with an explanation alone is going to be very "
+"confusing; but fortunately, BGB got us covered thanks to its *VRAM viewer*.\n"
+"You can open it either by selecting \"Window\" then \"VRAM viewer\", or "
+"pressing F5 when the debugger is in focus.\n"
+"It is also in \"Other\" in the screen's right-click menu."
+msgstr ""
+
+#: src/part1/tiles.md:30
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tiles.md:32
+msgid ""
+"By default, it should open on the \"Tiles\" tab like in the image, but if "
+"not, please select that.\n"
+"We will come to the other tabs in due time.\n"
+"This one shows the tiles present in the Game Boy's video memory (or \"VRAM\")."
+msgstr ""
+
+#: src/part1/tiles.md:38
+msgid ""
+"I encourage you to experiment with the VRAM viewer, hover over things, tick "
+"and untick checkboxes, see by yourself what's what. Any questions you might "
+"have will be answered in due time, don't worry! And if what you're seeing "
+"later on doesn't match my screenshots, ensure that the checkboxes match mine."
+msgstr ""
+
+#: src/part1/tiles.md:42
+msgid ""
+"Don't mind the \"Nintendo\" in the top-left; we did not put it there "
+"ourselves, and we will see why it's there later.\n"
+"We will also be ignoring the right half of the tile grid, as it is exclusive "
+"to the Game Boy Color: we will get there when we get there."
+msgstr ""
+
+#: src/part1/tiles.md:45
+msgid "## Short primer"
+msgstr ""
+
+#: src/part1/tiles.md:47
+msgid ""
+"You may have heard of tiles before, especially as they were really popular "
+"in 8-bit and 16-bit systems.\n"
+"That's no coincidence: tiles are very useful.\n"
+"Instead of storing every on-screen pixel (144 × 160 pixels × 2 bits/pixel = "
+"46080 bits = 5760 bytes, compared to the console's 8192 bytes of VRAM), "
+"pixels are grouped into tiles, and then tiles are assembled in various ways "
+"to produce the final image."
+msgstr ""
+
+#: src/part1/tiles.md:51
+msgid ""
+"In particular, tiles can be reused very easily and at basically no cost, "
+"saving a lot of memory!\n"
+"In addition, manipulating whole tiles at once is much cheaper than "
+"manipulating the individual pixels, so this spares processing time as well."
+msgstr ""
+
+#: src/part1/tiles.md:54
+msgid ""
+"The concept of a \"tile\" is very general, but on the Game Boy, tiles are "
+"*always* 8 by 8 pixels.\n"
+"Often, hardware tiles are grouped to manipulate them as larger tiles (often "
+"16×16); to avoid the confusion, those are referred to as **meta-tiles**."
+msgstr ""
+
+#: src/part1/tiles.md:57
+msgid "### \"bpp\"?"
+msgstr ""
+
+#: src/part1/tiles.md:59
+msgid ""
+"You may be wondering where that \"2 bits/pixel\" figure earlier came "
+"from...\n"
+"This is something called \"bit depth\"."
+msgstr ""
+
+#: src/part1/tiles.md:62
+msgid ""
+"See, colors are *not* stored in the tiles themselves!\n"
+"Instead, it works like a coloring book: the tile itself contains 8 by 8 "
+"*indices*, not colors; you give the hardware a tile and a set of colors—a "
+"**palette**—and it colorizes them!\n"
+"(This is also why color swaps were very common back then: you could create "
+"enemy variations by storing tiny palettes instead of large different "
+"graphics.)"
+msgstr ""
+
+#: src/part1/tiles.md:66
+msgid ""
+"Anyway, as it is, Game Boy palettes are 4 colors large.[^pal_size]\n"
+"This means that the indices into those palettes, stored in the tiles, can be "
+"represented in only *two bits*!\n"
+"This is called \"2 bits per pixel\", noted \"2bpp\"."
+msgstr ""
+
+#: src/part1/tiles.md:70
+msgid ""
+"With that in mind, we are ready to explain how these bytes turn into pixels!"
+msgstr ""
+
+#: src/part1/tiles.md:72
+msgid "## Encoding"
+msgstr ""
+
+#: src/part1/tiles.md:74
+msgid ""
+"As I explained, each pixel takes up 2 bits.\n"
+"Since there are 8 bits in a byte, you might expect each byte to contain 4 "
+"pixels... and you would be neither entirely right, nor entirely wrong.\n"
+"See, each row of 8 pixels is stored in 2 bytes, but neither of these bytes "
+"contains the info for 4 pixels.\n"
+"(Think of it like a 10 € banknote torn in half: neither half is worth "
+"anything, but the full bill is worth, well, 10 €.)"
+msgstr ""
+
+#: src/part1/tiles.md:79
+msgid ""
+"For each pixel, the least significant bit of its index is stored in the "
+"first byte, and the most significant bit is stored in the second byte.\n"
+"Since each byte is a collection of one of the bits for each pixel, it's "
+"called a **bitplane**."
+msgstr ""
+
+#: src/part1/tiles.md:82
+msgid ""
+"The leftmost pixel is stored in the leftmost bit of both bytes, the pixel to "
+"its right in the second leftmost bit, and so on.\n"
+"The first pair of bytes stores the topmost row, the second byte the row "
+"below that, and so on."
+msgstr ""
+
+#: src/part1/tiles.md:85
+msgid ""
+"Here is a more visual demonstration; note that it's for the SNES, but its "
+"2BPP format is the same as the Game Boy's."
+msgstr ""
+
+#: src/part1/tiles.md:87
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tiles.md:89
+msgid ""
+"This encoding may seem a little weird at first, and it can be; it's made to "
+"be more convenient for the hardware to decode, keeping the circuitry simple "
+"and low-power.\n"
+"It even makes a few cool tricks possible, as we will see (much) later!"
+msgstr ""
+
+#: src/part1/tiles.md:92
+msgid ""
+"You can read up more about the encoding [in the Pan Docs](https://gbdev.io/"
+"pandocs/Tile_Data.html) and [ShantyTown's site](https://www.huderlem.com/"
+"demos/gameboy2bpp.html)."
+msgstr ""
+
+#: src/part1/tiles.md:94
+msgid "In the next lesson, we shall see how colors are applied!"
+msgstr ""
+
+#: src/part1/tiles.md:98
+msgid ""
+"[^pal_size]:\n"
+"Other consoles can have varying bit depths; for example, the SNES has 2bpp, "
+"4bpp, and 8bpp depending on the graphics mode and a few other parameters."
+msgstr ""
+
+#: src/part1/palettes.md:1
+msgid "# Palettes"
+msgstr ""
+
+#: src/part1/palettes.md:3
+msgid ""
+"In the previous lesson, I briefly mentioned that colors are applied to tiles "
+"via *palettes*, but we haven't talked much about those yet."
+msgstr ""
+
+#: src/part1/palettes.md:5
+msgid ""
+"The black & white Game Boy has three palettes, one for the background called "
+"[`BGP`](https://gbdev.io/pandocs/Palettes.html#ff47---bgp-bg-palette-data-"
+"rw---non-cgb-mode-only) (\"BackGround Palette\"), and two for the objects "
+"called [`OBP0`](https://gbdev.io/pandocs/Palettes.html#ff48---obp0-object-"
+"palette-0-data-rw---non-cgb-mode-only) and [`OBP1`](https://gbdev.io/pandocs/"
+"Palettes.html#ff48---obp1-object-palette-1-data-rw---non-cgb-mode-only) "
+"(\"OBject Palette 0/1\").\n"
+"If you are wondering what \"objects\" are, you will have to wait until Part "
+"Ⅱ to find out; for now, let's focus on the background."
+msgstr ""
+
+#: src/part1/palettes.md:8
+msgid "::: tip:🌈"
+msgstr ""
+
+#: src/part1/palettes.md:10
+msgid ""
+"The Game Boy Color introduced, obviously, colors, and this was mainly done "
+"by reworking the way palettes are handled.\n"
+"We will not talk about Game Boy Color features in Part Ⅰ for the sake of "
+"simplicity, but we will do so in later parts."
+msgstr ""
+
+#: src/part1/palettes.md:15
+msgid "Please select the \"Palettes\" tab of BGB's VRAM viewer."
+msgstr ""
+
+#: src/part1/palettes.md:17
+msgid ""
+""
+msgstr ""
+
+#: src/part1/palettes.md:19
+msgid "::: tip:🥴"
+msgstr ""
+
+#: src/part1/palettes.md:21
+msgid ""
+"The VRAM viewer's layout may seem weird.\n"
+"As mentioned in the previous tip box, the Game Boy Color changed the way "
+"palettes work, and the Super Game Boy throws another wrench into the mix."
+msgstr ""
+
+#: src/part1/palettes.md:24
+msgid ""
+"The VRAM viewer uses the same layout for monochrome and Color, leading to "
+"this admittedly strange layout."
+msgstr ""
+
+#: src/part1/palettes.md:28
+msgid ""
+"We will be taking a look at the \"BGP\" line.\n"
+"As I explained before, tiles store \"color indices\" for each pixel, which "
+"are used to index into the palette.\n"
+"Color number 0[^numbering_zero] is the leftmost in that line, and number 3 "
+"is the rightmost."
+msgstr ""
+
+#: src/part1/palettes.md:32
+msgid ""
+"So, in our case, color number 0 is \"white\", color number 1 is \"light "
+"gray\", number 2 is \"dark gray\", and number 3 \"black\".\n"
+"I put air quotes because \"black\" isn't true black, and \"white\" isn't "
+"true white.\n"
+"Further, note that the original Game Boy had shades of green, but the later "
+"Game Boy Pocket's screen produced shades of gray instead.\n"
+"And, even better, the Game Boy Color will automatically colorize games that "
+"lack Game Boy Color support!"
+msgstr ""
+
+#: src/part1/palettes.md:37
+msgid ""
+""
+msgstr ""
+
+#: src/part1/palettes.md:39
+msgid ""
+"All this to say, one shouldn't expect specific colors out of a Game Boy "
+"game[^console_detection], just four more or less bright colors."
+msgstr ""
+
+#: src/part1/palettes.md:41
+msgid "## Getting our hands dirty"
+msgstr ""
+
+#: src/part1/palettes.md:43
+msgid ""
+"Well, so far in this tutorial, besides running the Hello World, we have been "
+"pretty passive, watching it unfold.\n"
+"What do you say we start prodding the ROM a bit?"
+msgstr ""
+
+#: src/part1/palettes.md:46
+msgid ""
+"In BGB's debugger, select the \"Window\" menu, and open the \"IO map\" (or "
+"just press F10 within the debugger)."
+msgstr ""
+
+#: src/part1/palettes.md:48
+msgid ""
+msgstr ""
+
+#: src/part1/palettes.md:50
+msgid ""
+"While the VRAM viewer offers a visual representation of the palette, the IO "
+"map shows the nitty-gritty: how it's encoded.\n"
+"The IO map also lets us modify BGP easily; but to do so, we need to "
+"understand *how* values we write are turned into colors."
+msgstr ""
+
+#: src/part1/palettes.md:53
+msgid "### Encoding"
+msgstr ""
+
+#: src/part1/palettes.md:55
+msgid ""
+"Fortunately, the encoding is very simple.\n"
+"I will explain it, and at the same time, give an example with the palette we "
+"have at hand, $E4."
+msgstr ""
+
+#: src/part1/palettes.md:58
+msgid ""
+"Take the byte, break its 8 bits into 4 groups of 2.\n"
+"```\n"
+"[BGP] = $E4\n"
+"$E4 = %11100100 (refresh your memory in the \"Binary and hexadecimal\" "
+"lesson if needed!)\n"
+"That gets broken down into %11, %10, %01, %00\n"
+"```"
+msgstr ""
+
+#: src/part1/palettes.md:65
+msgid ""
+"Color number 0 is the rightmost \"group\", color number 3 is the leftmost "
+"one.\n"
+"Simple!\n"
+"And this matches what the VRAM viewer is showing us: color number 0, the "
+"rightmost, is the brightest (%00), up to color number 3, the leftmost and "
+"the darkest (%11)."
+msgstr ""
+
+#: src/part1/palettes.md:69
+msgid "### Lights out"
+msgstr ""
+
+#: src/part1/palettes.md:71
+msgid ""
+"For fun, let's make the screen completely black.\n"
+"We can easily do this by setting all colors in the palette to black (%11).\n"
+"This would be `%11 %11 %11 %11 = $FF`."
+msgstr ""
+
+#: src/part1/palettes.md:75
+msgid ""
+"In the IO map, click the text box left of \"BGP\", erase the \"E4\", type "
+"\"FF\", and hit Enter.\n"
+"BGP immediately updates, turning the screen black!"
+msgstr ""
+
+#: src/part1/palettes.md:78
+msgid ""
+"\n"
+"\t\n"
+"\tObserve how the BGP line is entirely black now. Also, I could "
+"have shown a screenshot of the black screen, but that would have been silly."
+"\n"
+""
+msgstr ""
+
+#: src/part1/palettes.md:83
+msgid ""
+"What if we wanted to take the original palette, but invert it?\n"
+"%11 would become %00, %01 would become %10, %10 would become %01, and %00 "
+"would become %11.\n"
+"We would get thus:"
+msgstr ""
+
+#: src/part1/palettes.md:87
+msgid ""
+"```\n"
+"%11_10_01_00\n"
+" ↓ ↓ ↓ ↓\n"
+"%00_01_10_11\n"
+"```"
+msgstr ""
+
+#: src/part1/palettes.md:93
+msgid ""
+"(I'm not giving the value in hexadecimal, use this as an opportunity to "
+"exercise your bin-to-hex conversions!)"
+msgstr ""
+
+#: src/part1/palettes.md:95
+msgid ""
+"\n"
+"\t\n"
+"\tIf you got it right, it should look like this!\n"
+""
+msgstr ""
+
+#: src/part1/palettes.md:100
+msgid ""
+"If you go to the Tile Viewer and uncheck the \"show paletted\" checkbox, you "
+"will notice that the tile data stays the same regardless of how the palette "
+"is modified!\n"
+"This is an advantage of using palettes: fading the screen in and out is very "
+"cheap, just modifying a single byte, instead of having to update every "
+"single on-screen pixel."
+msgstr ""
+
+#: src/part1/palettes.md:103
+msgid ""
+"Got all that?\n"
+"Then let's take a look at the last missing puzzle piece in the Hello World's "
+"rendering process, the **tilemap**!"
+msgstr ""
+
+#: src/part1/palettes.md:108
+msgid ""
+"[^numbering_zero]:\n"
+"Numbering often starts at 0 when working with computers.\n"
+"We will understand why later, but for now, please bear with it!"
+msgstr ""
+
+#: src/part1/palettes.md:112
+msgid ""
+"[^console_detection]:\n"
+"Well, it is possible to detect these different models and account for them, "
+"but this would require taking plenty of corner cases into consideration, so "
+"it's probably not worth the effort."
+msgstr ""
+
+#: src/part1/tilemap.md:1
+msgid "# Tilemap"
+msgstr ""
+
+#: src/part1/tilemap.md:3
+msgid "::: tip:🧐"
+msgstr ""
+
+#: src/part1/tilemap.md:5
+msgid ""
+"Some spell them \"tile map\", some \"tilemap\".\n"
+"I will be using the latter by preference, but I also stay consistent with it "
+"in the code (`Tilemap` and not `TileMap`), as well as later when we will "
+"talk about attribute maps (\"attrmap\" and `Attrmap` instead of `AttrMap`)."
+msgstr ""
+
+#: src/part1/tilemap.md:10
+msgid ""
+"We are *almost* there.\n"
+"We have seen how graphics on the Game Boy are composed of 8×8 \"tiles\", and "
+"we have seen how color is added into the mix."
+msgstr ""
+
+#: src/part1/tilemap.md:13
+msgid ""
+"But we have not seen yet how those tiles are arranged into a final picture!"
+msgstr ""
+
+#: src/part1/tilemap.md:15
+msgid ""
+"Tiles are basically a grid of pixels; well, the tilemaps are basically a "
+"grid of tiles!\n"
+"To allow for cheap reuse, tiles aren't stored in the tilemap directly; "
+"instead, tiles are referred to by an *ID*, which you can see in BGB's VRAM "
+"viewer."
+msgstr ""
+
+#: src/part1/tilemap.md:18
+msgid ""
+"\n"
+" \n"
+" \n"
+" The ID is displayed in hexadecimal without a prefix, so this is tile "
+"number $10, aka 16.\n"
+" As you may have noticed, the tiles are displayed in rows of 16, so it's "
+"easier to locate them by hexadecimal ID.\n"
+" Nifty!\n"
+" \n"
+""
+msgstr ""
+
+#: src/part1/tilemap.md:27
+msgid ""
+"Now, of course, tile IDs are numbers, like everything that computers deal "
+"with.\n"
+"IDs are stored in bytes, so there are 256 possible tile IDs.\n"
+"However, the astute reader will have noticed that there are 384 tiles in "
+"total[^tile_blocks]!\n"
+"By virtue of the [pigeonhole principle](https://en.wikipedia.org/wiki/"
+"Pigeonhole_principle), this means that some IDs refer to several tiles at "
+"the same time."
+msgstr ""
+
+#: src/part1/tilemap.md:32
+msgid ""
+"Indeed, BGB reports that the first 128 tiles have the same IDs as the last "
+"128.\n"
+"There exists a mechanism to select whether IDs 0–127 reference the first or "
+"last 128 tiles, but for simplicity's sake, we will overlook this for now, so "
+"please ignore the first (topmost) 128 tiles for the time being."
+msgstr ""
+
+#: src/part1/tilemap.md:35
+msgid ""
+"Now, please turn your attention to the \"BG map\" tab of BGB's VRAM viewer, "
+"pictured below."
+msgstr ""
+
+#: src/part1/tilemap.md:37
+msgid ""
+msgstr ""
+
+#: src/part1/tilemap.md:41
+msgid ""
+"You may notice that the image shown is larger than what is displayed on-"
+"screen.\n"
+"Only part of the tilemap, outlined by a thicker border in the VRAM viewer, "
+"is displayed on-screen at a given time.\n"
+"We will explain this in more detail in Part Ⅱ."
+msgstr ""
+
+#: src/part1/tilemap.md:47
+msgid ""
+"Here we will be able to see the power of tile reuse in full force.\n"
+"As a convenience and a refresher, here are the tiles our Hello World loads "
+"into VRAM:"
+msgstr ""
+
+#: src/part1/tilemap.md:50
+msgid ""
+""
+msgstr ""
+
+#: src/part1/tilemap.md:52
+msgid ""
+"You can see that we only loaded a single \"blank\" tile ($00, the first aka. "
+"top-left one), but it can be repeated to cover the whole background at no "
+"extra cost!"
+msgstr ""
+
+#: src/part1/tilemap.md:54
+msgid ""
+"Repetition can be more subtle: for example, tile $01 is used for the top-"
+"left corner of the H, E, L, L, and W (red lines below)!\n"
+"The R, L, and D also both share their top-left tile ($2D, blue lines below); "
+"and so on.\n"
+"You can confirm this by hovering over tiles in the BG map tab, which shows "
+"the ID of the tile at that position."
+msgstr ""
+
+#: src/part1/tilemap.md:58
+msgid ""
+"\n"
+" \n"
+" \n"
+" Here are some examples of tile reuse. Not everything is drawn, as it "
+"would become a mess.\n"
+" \n"
+""
+msgstr ""
+
+#: src/part1/tilemap.md:65
+msgid ""
+"All in all, we can surmise that displaying graphics on the Game Boy consists "
+"of loading \"patterns\" (the tiles), and then telling the console which tile "
+"to display for each given location."
+msgstr ""
+
+#: src/part1/tilemap.md:69
+msgid ""
+"[^tile_blocks]:\n"
+"The even more astute (astuter?) reader will have noticed that 384 = 3 × "
+"128.\n"
+"Thus, tiles are often conceptually grouped into three \"blocks\" of 128 "
+"tiles each, which BGB shows as separated by thicker horizontal lines."
+msgstr ""
+
+#: src/part1/wrapup.md:1
+msgid "# Wrapping up"
+msgstr ""
+
+#: src/part1/wrapup.md:3
+msgid ""
+"Congrats!\n"
+"You have made it through the first part of this tutorial.\n"
+"By this point, you have a basic enough understanding of the console that you "
+"know how to display a picture.\n"
+"And hey, that doesn't sound like much, but consider everything you have seen "
+"so far—there *is* a lot that goes into it!"
+msgstr ""
+
+#: src/part1/wrapup.md:8
+msgid "::: tip:🥳"
+msgstr ""
+
+#: src/part1/wrapup.md:10
+msgid ""
+"Honestly, congrats on coming this far—many people have given up earlier than "
+"this.\n"
+"So you can give yourself a pat on the back, you honestly deserve it!\n"
+"**Now may also be a good time to take a break** if you are reading all this "
+"in a single trait.\n"
+"I encourage you to give it a little time to sink in, and maybe go back to "
+"the lessons you struggled on the most.\n"
+"Maybe a second read can help."
+msgstr ""
+
+#: src/part1/wrapup.md:20
+msgid ""
+"And yes, you could simply have let a library handle all that.\n"
+"However, the details always leak through eventually, so knowing about them "
+"is helpful, if only for debugging."
+msgstr ""
+
+#: src/part1/wrapup.md:23
+msgid ""
+"Plus, understanding what's really going on under the hood makes you a better "
+"programmer, even if you don't end up using ASM in the long run.\n"
+"Amusingly, even modern systems work similarly to older ones in unexpected "
+"places, so some things you just learned will carry over!\n"
+"Trust me, everything you have learned and will learn is worth it! ✊"
+msgstr ""
+
+#: src/part1/wrapup.md:27
+msgid ""
+"That said, right now, you may have a lot of questions.\n"
+"- Why do we turn off the LCD?\n"
+"- We know how to make a static picture, but how to we add motion into the "
+"mix?\n"
+"- Also, how do I get input from the player?\n"
+"- The code mentions shutting down audio, but how do I play some of those "
+"famed beeps and bloops?\n"
+"- Writing graphics in that way sound tedious, is there no other way?\n"
+"- Actually, wait, how do we make a game out of all this??"
+msgstr ""
+
+#: src/part1/wrapup.md:35
+msgid "... All of that answered, and more, in Part Ⅱ! 👀"
+msgstr ""
+
+#: src/part2/getting-started.md:1
+msgid "# Getting started"
+msgstr ""
+
+#: src/part2/getting-started.md:3
+msgid ""
+"In this lesson, we will start a new project from scratch.\n"
+"We will make a [Breakout](https://en.wikipedia.org/wiki/"
+"Breakout_%28video_game%29) / [Arkanoid](https://en.wikipedia.org/wiki/"
+"Arkanoid) clone, which we'll call \"Unbricked\"!\n"
+"(Though you are free to give it any other name you like, as it will be "
+"*your* project.)"
+msgstr ""
+
+#: src/part2/getting-started.md:7
+msgid ""
+"Open a terminal and make a new directory (`mkdir unbricked`), and then enter "
+"it (`cd unbricked`), just like you did for [\"Hello, world!\"](../part1/"
+"hello_world.md)."
+msgstr ""
+
+#: src/part2/getting-started.md:9
+msgid ""
+"Start by creating a file called `main.asm`, and include `hardware.inc` in "
+"your code."
+msgstr ""
+
+#: src/part2/getting-started.md:11
+msgid ""
+"```rgbasm,linenos,start=1\n"
+"INCLUDE \"hardware.inc\"\n"
+"```\n"
+"You may be wondering what purpose `hardware.inc` serves.\n"
+"Well, the code we write only really affects the CPU, but does not do "
+"anything with the rest of the console (not directly, anyway).\n"
+"To interact with other components (like the graphics system, say), [Memory-"
+"Mapped I/O](https://en.wikipedia.org/"
+"wiki/Memory-mapped_I/O) (MMIO) is used: basically, [memory](../part1/memory."
+"md) in a certain range (addresses $FF00–FF7F) does special things when "
+"accessed."
+msgstr ""
+
+#: src/part2/getting-started.md:18
+msgid ""
+"These bytes of memory being interfaces to the hardware, they are called "
+"*hardware registers* (not to be mistaken with [the CPU registers](../part1/"
+"registers.md)).\n"
+"For example, the \"PPU status\" register is located at address $FF41.\n"
+"Reading from that address reports various bits of info regarding the "
+"graphics system, and writing to it allows changing some parameters.\n"
+"But, having to remember all the numbers ([non-exhaustive list](https://gbdev."
+"io/pandocs/Power_Up_Sequence.html#hardware-registers)) would be very tedious—"
+"and this is where `hardware.inc` comes into play!\n"
+"`hardware.inc` defines one constant for each of these registers (for "
+"example, `rSTAT` for the aforementioned \"PPU status\" register), plus some "
+"additional constants for values read from or written to these registers."
+msgstr ""
+
+#: src/part2/getting-started.md:26
+msgid ""
+"Don't worry if this flew over your head, we'll see an example below with "
+"`rLCDC` and `LCDCF_ON`."
+msgstr ""
+
+#: src/part2/getting-started.md:28
+msgid ""
+"By the way, the `r` stands for \"register\", and the `F` in `LCDCF` stands "
+"for \"flag\"."
+msgstr ""
+
+#: src/part2/getting-started.md:32
+msgid ""
+"Next, make room for the header.\n"
+"[Remember from Part Ⅰ](../part1/header.md) that the header is where some "
+"information that the Game Boy relies on is stored, so you don't want to "
+"accidentally leave it out."
+msgstr ""
+
+#: src/part2/getting-started.md:43
+msgid "The header jumps to `EntryPoint`, so let's write that now:"
+msgstr ""
+
+#: src/part2/getting-started.md:45
+msgid ""
+"```rgbasm,linenos,start=9\n"
+"EntryPoint:\n"
+"\t; Do not turn the LCD off outside of VBlank\n"
+"WaitVBlank:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp c, WaitVBlank"
+msgstr ""
+
+#: src/part2/getting-started.md:53
+msgid ""
+"\t; Turn the LCD off\n"
+"\tld a, 0\n"
+"\tld [rLCDC], a\n"
+"```"
+msgstr ""
+
+#: src/part2/getting-started.md:58
+msgid ""
+"The next few lines wait until \"VBlank\", which is the only time you can "
+"safely turn off the screen (doing so at the wrong time could damage a real "
+"Game Boy, so this is very crucial).\n"
+"We'll explain what VBlank is and talk about it more later in the tutorial."
+msgstr ""
+
+#: src/part2/getting-started.md:61
+msgid ""
+"Turning off the screen is important because loading new tiles while the "
+"screen is on is tricky—we'll touch on how to do that in Part 3."
+msgstr ""
+
+#: src/part2/getting-started.md:63
+msgid ""
+"Speaking of tiles, we're going to load some into VRAM next, using the "
+"following code:"
+msgstr ""
+
+#: src/part2/getting-started.md:65 src/part2/functions.md:41
+msgid ""
+"```rgbasm,linenos,start=20\n"
+"\t; Copy the tile data\n"
+"\tld de, Tiles\n"
+"\tld hl, $9000\n"
+"\tld bc, TilesEnd - Tiles\n"
+"CopyTiles:\n"
+"\tld a, [de]\n"
+"\tld [hli], a\n"
+"\tinc de\n"
+"\tdec bc\n"
+"\tld a, b\n"
+"\tor a, c\n"
+"\tjp nz, CopyTiles\n"
+"```"
+msgstr ""
+
+#: src/part2/getting-started.md:80
+msgid ""
+"This loop might be [reminiscent of part Ⅰ](../part1/jumps.md#conditional-"
+"jumps).\n"
+"It copies starting at `Tiles` to `$9000` onwards, which is the part of VRAM "
+"where our [tiles](../part1/tiles.md) are going to be stored.\n"
+"Recall that `$9000` is where the data of background tile $00 lies, and the "
+"data of subsequent tiles follows right after.\n"
+"To get the number of bytes to copy, we will do just like in Part Ⅰ: using "
+"another label at the end, called `TilesEnd`, the difference between it (= "
+"the address after the last byte of tile data) and `Tiles` (= the address of "
+"the first byte) will be exactly that length."
+msgstr ""
+
+#: src/part2/getting-started.md:85
+msgid ""
+"That said, we haven't written `Tiles` nor any of the related data yet.\n"
+"We'll get to that later!"
+msgstr ""
+
+#: src/part2/getting-started.md:88
+msgid ""
+"Almost done now—next, write another loop, this time for copying [the tilemap]"
+"(../part1/tilemap.md)."
+msgstr ""
+
+#: src/part2/getting-started.md:90 src/part2/functions.md:68
+msgid ""
+"```rgbasm,linenos,start=33\n"
+"\t; Copy the tilemap\n"
+"\tld de, Tilemap\n"
+"\tld hl, $9800\n"
+"\tld bc, TilemapEnd - Tilemap\n"
+"CopyTilemap:\n"
+"\tld a, [de]\n"
+"\tld [hli], a\n"
+"\tinc de\n"
+"\tdec bc\n"
+"\tld a, b\n"
+"\tor a, c\n"
+"\tjp nz, CopyTilemap\n"
+"```"
+msgstr ""
+
+#: src/part2/getting-started.md:105
+msgid ""
+"Note that while this loop's body is exactly the same as `CopyTiles`'s, the 3 "
+"values loaded into `de`, `hl`, and `bc` are different.\n"
+"These determine the source, destination, and size of the copy, respectively."
+msgstr ""
+
+#: src/part2/getting-started.md:108
+msgid ""
+"::: tip \"[DRY](https://en."
+"wikipedia.org/wiki/Don't_Repeat_Yourself)\""
+msgstr ""
+
+#: src/part2/getting-started.md:110
+msgid ""
+"If you think that this is super redundant, you are not wrong, and we will "
+"see later how to write actual, reusable *functions*.\n"
+"But there is more to them than meets the eye, so we will start tackling them "
+"much later."
+msgstr ""
+
+#: src/part2/getting-started.md:115
+msgid ""
+"Finally, let's turn the screen back on, and set a [background palette](../"
+"part1/palettes.md).\n"
+"Rather than writing the non-descript number `%10000001` (or $81 or 129, to "
+"taste), we make use of two constants graciously provided by `hardware.inc`: "
+"`LCDCF_ON` and `LCDCF_BGON`.\n"
+"When written to [`rLCDC`](https://gbdev.io/pandocs/LCDC), the former causes "
+"the PPU and screen to turn back on, and the latter enables the background to "
+"be drawn.\n"
+"(There are other elements that could be drawn, but we are not enabling them "
+"yet.)\n"
+"Combining these constants must be done using `|`, the *binary \"or\"* "
+"operator; we'll see why later."
+msgstr ""
+
+#: src/part2/getting-started.md:121
+msgid ""
+"```rgbasm,linenos,start=46\n"
+"\t; Turn the LCD on\n"
+"\tld a, LCDCF_ON | LCDCF_BGON\n"
+"\tld [rLCDC], a"
+msgstr ""
+
+#: src/part2/getting-started.md:126
+msgid ""
+"\t; During the first (blank) frame, initialize display registers\n"
+"\tld a, %11100100\n"
+"\tld [rBGP], a"
+msgstr ""
+
+#: src/part2/getting-started.md:130
+msgid ""
+"Done:\n"
+"\tjp Done\n"
+"```"
+msgstr ""
+
+#: src/part2/getting-started.md:134
+msgid ""
+"There's one last thing we need before we can build the ROM, and that's the "
+"graphics.\n"
+"We will draw the following screen:"
+msgstr ""
+
+#: src/part2/getting-started.md:137
+msgid ""
+msgstr ""
+
+#: src/part2/getting-started.md:139
+msgid ""
+"In `hello-world.asm`, tile data had been written out by hand in hexadecimal; "
+"this was to let you see how the sausage is made at the lowest level, but "
+"*boy* is it impractical to write!\n"
+"This time, we will employ a more friendly way, which will let us write each "
+"row of pixels more easily.\n"
+"For each row of pixels, instead of writing [the bitplanes](../part1/tiles."
+"md#encoding) directly, we will use a backtick (`` ` ``) followed by 8 "
+"characters.\n"
+"Each character defines a single pixel, intuitively from left to right; it "
+"must be one of 0, 1, 2, and 3, representing the corresponding color index in "
+"[the palette](../part1/palettes.md)."
+msgstr ""
+
+#: src/part2/getting-started.md:146
+msgid ""
+"If the character selection isn't to your liking, you can use [RGBASM's `-g` "
+"option](https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.1#g) or [`OPT g`](https://"
+"rgbds.gbdev.io/docs/v0.5.2/rgbasm.5/#Changing_options_while_assembling) to "
+"pick others.\n"
+"For example, `rgbasm -g '.xXO' (...)` or `OPT g.xXO` would swap the four "
+"characters to `.`, `x`, `X`, and `O` respectively."
+msgstr ""
+
+#: src/part2/getting-started.md:151
+msgid "For example:"
+msgstr ""
+
+#: src/part2/getting-started.md:153
+msgid ""
+"```rgbasm\n"
+"\tdw `01230123 ; This is equivalent to `db $55,$33`\n"
+"```"
+msgstr ""
+
+#: src/part2/getting-started.md:157
+msgid ""
+"You may have noticed that we are using `dw` instead of `db`; the difference "
+"between these two will be explained later.\n"
+"We already have tiles made for this project, so you can copy [this premade "
+"file](https://github.com/ISSOtm/gb-asm-tutorial/raw/master/unbricked/getting-"
+"started/tileset.asm), and paste it at the end of your code."
+msgstr ""
+
+#: src/part2/getting-started.md:160
+msgid ""
+"Then copy the tilemap from [this file](https://github.com/ISSOtm/gb-asm-"
+"tutorial/raw/master/unbricked/getting-started/tilemap.asm), and paste it "
+"after the `TilesEnd` label."
+msgstr ""
+
+#: src/part2/getting-started.md:162
+msgid ""
+"You can build the ROM now, by running the following commands in your "
+"terminal:"
+msgstr ""
+
+#: src/part2/getting-started.md:164
+msgid ""
+"```console\n"
+"$ rgbasm -L -o main.o main.asm\n"
+"$ rgblink -o unbricked.gb main.o\n"
+"$ rgbfix -v -p 0xFF unbricked.gb\n"
+"```"
+msgstr ""
+
+#: src/part2/getting-started.md:170
+msgid "If you run this in your emulator, you should see the following:"
+msgstr ""
+
+#: src/part2/getting-started.md:172
+msgid ""
+msgstr ""
+
+#: src/part2/getting-started.md:174
+msgid ""
+"That white square seems to be missing!\n"
+"You may have noticed this comment earlier, somewhere in the tile data:"
+msgstr ""
+
+#: src/part2/getting-started.md:177
+msgid ""
+"```rgbasm,linenos,start=135\n"
+"\tdw `22322232\n"
+"\tdw `23232323\n"
+"\tdw `33333333\n"
+"\t; Paste your logo here:"
+msgstr ""
+
+#: src/part2/getting-started.md:183
+msgid ""
+"TilesEnd:\n"
+"```"
+msgstr ""
+
+#: src/part2/getting-started.md:186
+msgid ""
+"The logo tiles were left intentionally blank so that you can choose your "
+"own.\n"
+"You can use one of the following pre-made logos, or try coming up with your "
+"own!"
+msgstr ""
+
+#: src/part2/getting-started.md:189
+msgid "- **RGBDS Logo**"
+msgstr ""
+
+#: src/part2/getting-started.md:191
+msgid " "
+msgstr ""
+
+#: src/part2/getting-started.md:193
+msgid ""
+" [Source](https://github.com/ISSOtm/gb-asm-tutorial/raw/master/unbricked/"
+"getting-started/rgbds.asm)"
+msgstr ""
+
+#: src/part2/getting-started.md:195
+msgid "- **Duck**"
+msgstr ""
+
+#: src/part2/getting-started.md:197
+msgid " "
+msgstr ""
+
+#: src/part2/getting-started.md:199
+msgid ""
+" [Source](https://github.com/ISSOtm/gb-asm-tutorial/raw/master/unbricked/"
+"getting-started/duck.asm)"
+msgstr ""
+
+#: src/part2/getting-started.md:201
+msgid "- **Tail**"
+msgstr ""
+
+#: src/part2/getting-started.md:203
+msgid " "
+msgstr ""
+
+#: src/part2/getting-started.md:205
+msgid ""
+" [Source](https://github.com/ISSOtm/gb-asm-tutorial/raw/master/unbricked/"
+"getting-started/tail.asm)"
+msgstr ""
+
+#: src/part2/getting-started.md:207
+msgid ""
+"Add your chosen logo's data (click one of the \"Source\" links above) after "
+"the comment, build the game again, and you should see your logo of choice in "
+"the bottom-right!"
+msgstr ""
+
+#: src/part2/objects.md:1
+msgid "# Objects"
+msgstr ""
+
+#: src/part2/objects.md:3
+msgid ""
+"The background is very useful when the whole screen should move at once, but "
+"this is not ideal for everything.\n"
+"For example, a cursor in a menu, NPCs and the player in a RPG, bullets in a "
+"shmup, or balls in an *Arkanoid* clone... all need to move independently of "
+"the background.\n"
+"Thankfully, the Game Boy has a feature that's perfect for these!\n"
+"In this lesson, we will talk about *objects* (sometimes called \"OBJ\")."
+msgstr ""
+
+#: src/part2/objects.md:10
+msgid ""
+"The above description may have made you think of the term \"sprite\" instead "
+"of \"object\".\n"
+"The term \"sprite\" has a *lot* of meanings depending on context, so, to "
+"avoid confusion, this tutorial tries to use specific alternatives instead, "
+"such as *object*, *metasprite*, *actor*, etc."
+msgstr ""
+
+#: src/part2/objects.md:15
+msgid ""
+"Each object allows drawing one or two tiles (so 8×8 or 8×16 pixels, "
+"respectively) at any on-screen position—unlike the background, where all the "
+"tiles are drawn in a grid.\n"
+"Therefore, an object consists of its on-screen position, a tile ID (like "
+"[with the tilemap](../part1/tilemap.md)), and some extra properties called "
+"\"attributes\".\n"
+"These extra properties allow, for example, to display the tile flipped.\n"
+"We'll see more about them later."
+msgstr ""
+
+#: src/part2/objects.md:20
+msgid ""
+"Just like how the tilemap is stored in VRAM, objects live in a region of "
+"memory called OAM, meaning **Object Attribute Memory**.\n"
+"Recall from above that an object consists of:\n"
+"- Its on-screen position\n"
+"- A tile ID\n"
+"- The \"attributes\""
+msgstr ""
+
+#: src/part2/objects.md:26
+msgid ""
+"These are stored in 4 bytes: one for the Y coordinate, one for the X "
+"coordinate, one for the tile ID, and one for the attributes.\n"
+"OAM is 160 bytes long, and since 160 ∕ 4 = 40, the Game Boy stores a total "
+"of **40** objects at any given time."
+msgstr ""
+
+#: src/part2/objects.md:29
+msgid ""
+"There is a catch, though: an object's Y and X coordinate bytes in OAM do "
+"*not* store its on-screen position!\n"
+"Instead, the *on-screen* X position is the *stored* X position **minus 8**, "
+"and the *on-screen* Y position is the *stored* Y position **minus 16**.\n"
+"To stop displaying an object, we can simply put it off-screen, e.g. by "
+"setting its Y position to 0."
+msgstr ""
+
+#: src/part2/objects.md:35
+msgid ""
+"These offsets are not arbitrary!\n"
+"Consider an object's maximum size: 8 by 16 pixels.\n"
+"These offsets allow objects to be clipped by the left and top edges of the "
+"screen.\n"
+"The NES, for example, lacks such offsets, so you will notice that objects "
+"always disappear after hitting the left or top edge of the screen."
+msgstr ""
+
+#: src/part2/objects.md:42
+msgid "Let's discover objects by experimenting with them!"
+msgstr ""
+
+#: src/part2/objects.md:44
+msgid ""
+"First off, when the Game Boy is powered on, OAM is filled with a bunch of "
+"semi-random values, which may cover the screen with some random garbage.\n"
+"Let's fix that by first clearing OAM before enabling objects for the first "
+"time.\n"
+"Let's add the following just after the `CopyTilemap` loop:"
+msgstr ""
+
+#: src/part2/objects.md:48
+msgid ""
+"```rgbasm\n"
+"\tld a, 0\n"
+"\tld b, 160\n"
+"\tld hl, _OAMRAM\n"
+"ClearOam:\n"
+"\tld [hli], a\n"
+"\tdec b\n"
+"\tjp nz, ClearOam\n"
+"```"
+msgstr ""
+
+#: src/part2/objects.md:58
+msgid ""
+"This is a good time to do that, since just like VRAM, the screen must be off "
+"to safely access OAM."
+msgstr ""
+
+#: src/part2/objects.md:60
+msgid "Once OAM is clear, we can draw an object by writing its properties."
+msgstr ""
+
+#: src/part2/objects.md:62
+msgid ""
+"```rgbasm,linenos,start=67\n"
+"\tld hl, _OAMRAM\n"
+"\tld a, 128 + 16\n"
+"\tld [hli], a\n"
+"\tld a, 16 + 8\n"
+"\tld [hli], a\n"
+"\tld a, 0\n"
+"\tld [hli], a\n"
+"\tld [hl], a\n"
+"```"
+msgstr ""
+
+#: src/part2/objects.md:73
+msgid ""
+"Remember that each object in OAM is 4 bytes, in the order Y, X, Tile ID, "
+"Attributes.\n"
+"So, the object's top-left pixel lies 128 pixels from the top of the screen, "
+"and 16 from its left.\n"
+"The tile ID and attributes are both set to 0."
+msgstr ""
+
+#: src/part2/objects.md:77
+msgid ""
+"You may remember from the previous lesson that we're already using tile ID "
+"0, as it's the start of our background's graphics.\n"
+"However, by default objects and backgrounds use a different set of tiles, at "
+"least for the first 128 IDs.\n"
+"Tiles with IDs 128–255 are shared by both, which is useful if you have a "
+"tile that's used both on the background and by an object."
+msgstr ""
+
+#: src/part2/objects.md:81
+msgid ""
+"If you press F5 in BGB to open the VRAM viewer, you should see three "
+"distinct sections."
+msgstr ""
+
+#: src/part2/objects.md:83
+msgid ""
+""
+msgstr ""
+
+#: src/part2/objects.md:85
+msgid ""
+"Because we need to load this to a different area, we'll use the address "
+"$8000 and load a graphic for our game's paddle.\n"
+"Let's do so right after `CopyTilemap`:"
+msgstr ""
+
+#: src/part2/objects.md:88 src/part2/functions.md:95
+msgid ""
+"```rgbasm,linenos,start=46\n"
+"\t; Copy the tile data\n"
+"\tld de, Paddle\n"
+"\tld hl, $8000\n"
+"\tld bc, PaddleEnd - Paddle\n"
+"CopyPaddle:\n"
+"\tld a, [de]\n"
+"\tld [hli], a\n"
+"\tinc de\n"
+"\tdec bc\n"
+"\tld a, b\n"
+"\tor a, c\n"
+"\tjp nz, CopyPaddle\n"
+"```"
+msgstr ""
+
+#: src/part2/objects.md:103
+msgid "And don't forget to add `Paddle` to the bottom of your code."
+msgstr ""
+
+#: src/part2/objects.md:105
+msgid ""
+"```rgbasm\n"
+"Paddle:\n"
+"\tdw `13333331\n"
+"\tdw `30000003\n"
+"\tdw `13333331\n"
+"\tdw `00000000\n"
+"\tdw `00000000\n"
+"\tdw `00000000\n"
+"\tdw `00000000\n"
+"\tdw `00000000\n"
+"PaddleEnd:\n"
+"```"
+msgstr ""
+
+#: src/part2/objects.md:118
+msgid ""
+"Finally, let's enable objects and see the result.\n"
+"Objects must be enabled by the familiar `rLCDC` register, otherwise they "
+"just don't show up.\n"
+"(This is why we didn't have to clear OAM in the previous lessons.)\n"
+"We will also need to initialize one of the object palettes, `rOBP0`.\n"
+"There are actually two object palettes, but we're only going to use one."
+msgstr ""
+
+#: src/part2/objects.md:124
+msgid ""
+"```rgbasm,linenos,start=76\n"
+"\t; Turn the LCD on\n"
+"\tld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJON\n"
+"\tld [rLCDC], a"
+msgstr ""
+
+#: src/part2/objects.md:129
+msgid ""
+"\t; During the first (blank) frame, initialize display registers\n"
+"\tld a, %11100100\n"
+"\tld [rBGP], a\n"
+"\tld a, %11100100\n"
+"\tld [rOBP0], a\n"
+"```"
+msgstr ""
+
+#: src/part2/objects.md:136
+msgid "## Movement"
+msgstr ""
+
+#: src/part2/objects.md:138
+msgid ""
+"Now that you have an object on the screen, let's move it around.\n"
+"Previously, the `Done` loop did nothing; let's rename it to `Main` and use "
+"it to move our object.\n"
+"We're going to wait for VBlank before changing OAM, just like we did before "
+"turning off the screen."
+msgstr ""
+
+#: src/part2/objects.md:142
+msgid ""
+"```rgbasm,linenos,start=89\n"
+"Main:\n"
+" ; Wait until it's *not* VBlank\n"
+" ld a, [rLY]\n"
+" cp 144\n"
+" jp nc, Main\n"
+"WaitVBlank2:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp c, WaitVBlank2"
+msgstr ""
+
+#: src/part2/objects.md:153 src/part2/objects.md:209
+msgid ""
+"\t; Move the paddle one pixel to the right.\n"
+"\tld a, [_OAMRAM + 1]\n"
+"\tinc a\n"
+"\tld [_OAMRAM + 1], a\n"
+"\tjp Main\n"
+"```"
+msgstr ""
+
+#: src/part2/objects.md:160
+msgid "::: tip:🤨"
+msgstr ""
+
+#: src/part2/objects.md:162
+msgid ""
+"Here, we are accessing OAM without turning the LCD off, but it's still "
+"safe.\n"
+"Explaining why requires a more thorough explanation of the Game Boy's "
+"rendering, so let's ignore it for now."
+msgstr ""
+
+#: src/part2/objects.md:167
+msgid ""
+"Now you should see the paddle moving... very quickly.\n"
+"Because it moves by a pixel ever frame, it's going at a speed of 60 pixels "
+"per second!\n"
+"To slow this down, we'll use a *variable*."
+msgstr ""
+
+#: src/part2/objects.md:171
+msgid ""
+"So far, we have only worked with the CPU registers, but you can create "
+"global variables too!\n"
+"To do this, let's create another section, but putting it in `WRAM0` instead "
+"of `ROM0`.\n"
+"Unlike ROM (\"Read-Only Memory\"), RAM (\"Random-Access Memory\") can be "
+"written to; thus, WRAM, or Work RAM, is where we can store our game's "
+"variables."
+msgstr ""
+
+#: src/part2/objects.md:175
+msgid "Add this to the bottom of your file:"
+msgstr ""
+
+#: src/part2/objects.md:177
+msgid ""
+"```rgbasm,linenos,start=358\n"
+"SECTION \"Counter\", WRAM0\n"
+"wFrameCounter: db\n"
+"```"
+msgstr ""
+
+#: src/part2/objects.md:182
+msgid ""
+"Now we'll use the `wFrameCounter` variable to count how many frames have "
+"passed since we last moved the paddle.\n"
+"Every 10th frame, we'll move the paddle by one pixel, slowing it down to 6 "
+"pixels per second.\n"
+"Don't forget that RAM is filled with garbage values when the Game Boy "
+"starts, so we need to initialize our variables before first using them."
+msgstr ""
+
+#: src/part2/objects.md:186
+msgid ""
+"```rgbasm,linenos,start=86\n"
+"\tld a, 0\n"
+"\tld [wFrameCounter], a"
+msgstr ""
+
+#: src/part2/objects.md:190
+msgid ""
+"Main:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp nc, Main\n"
+"WaitVBlank2:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp c, WaitVBlank2"
+msgstr ""
+
+#: src/part2/objects.md:199
+msgid ""
+"\tld a, [wFrameCounter]\n"
+"\tinc a\n"
+"\tld [wFrameCounter], a\n"
+"\tcp a, 15 ; Every 15 frames (a quarter of a second), run the following "
+"code\n"
+"\tjp nz, Main"
+msgstr ""
+
+#: src/part2/objects.md:205
+msgid ""
+"\t; Reset the frame counter back to 0\n"
+"\tld a, 0\n"
+"\tld [wFrameCounter], a"
+msgstr ""
+
+#: src/part2/objects.md:216
+msgid ""
+"Alright!\n"
+"Up next is us taking control of that little paddle."
+msgstr ""
+
+#: src/part2/functions.md:1
+msgid "# Functions"
+msgstr ""
+
+#: src/part2/functions.md:3
+msgid ""
+"So far, we have only written a single \"flow\" of code, but we can already "
+"spot some snippets that look redundant.\n"
+"Let's use **functions** to \"factor out\" code!"
+msgstr ""
+
+#: src/part2/functions.md:6
+msgid ""
+"For example, in three places, we are copying chunks of memory around.\n"
+"Let's write a function below the `jp Main`, and let's call it `Memcpy`, like "
+"[the similar C function](https://man7.org/linux/man-pages/man3/memcpy.3."
+"html):"
+msgstr ""
+
+#: src/part2/functions.md:9
+msgid ""
+"```rgbasm,linenos,start=93\n"
+"; Copy bytes from one area to another.\n"
+"; @param de: Source\n"
+"; @param hl: Destination\n"
+"; @param bc: Length\n"
+"Memcopy:\n"
+"\tld a, [de]\n"
+"\tld [hli], a\n"
+"\tinc de\n"
+"\tdec bc\n"
+"\tld a, b\n"
+"\tor a, c\n"
+"\tjp nz, Memcopy\n"
+"\tret\n"
+"```"
+msgstr ""
+
+#: src/part2/functions.md:25
+msgid ""
+"The new `ret` instruction should immediately catch our eye.\n"
+"It is, unsurprisingly, what makes execution *return* to where the function "
+"was *called* from.\n"
+"Importantly, many languages have a definite \"end\" to a function: in C or "
+"Rust, that's the closing brace `}`; in Pascal or Lua, the keyword `end`, and "
+"so on; the function implicitly returns when execution reaches its end.\n"
+"However, **this is not the case in assembly**, so you must remember to add a "
+"`ret` instruction at the end of the function to return from it!\n"
+"Otherwise, the results are unpredictable."
+msgstr ""
+
+#: src/part2/functions.md:31
+msgid ""
+"Notice the comment above the function, explaining which registers it takes "
+"as input.\n"
+"This comment is important so that you know how to interface with the "
+"function; assembly has no formal parameters, so comments explaining them are "
+"even more important than with other languages.\n"
+"We'll see more of those as we progress."
+msgstr ""
+
+#: src/part2/functions.md:35
+msgid ""
+"There are three places in the initialization code where we can use the "
+"`Memcpy` function.\n"
+"Find each of these copy loops and replace them with a call to `Memcpy`; for "
+"this, we use the `call` instruction.\n"
+"The registers serve as parameters to the function, so we'll leave them as-is."
+msgstr ""
+
+#: src/part2/functions.md:39
+msgid ""
+"
"
+msgstr ""
+
+#: src/part2/functions.md:122
+msgid ""
+"In the next chapter, we'll write another function, this time to read player "
+"input."
+msgstr ""
+
+#: src/part2/input.md:1
+msgid "# Input"
+msgstr ""
+
+#: src/part2/input.md:3
+msgid ""
+"We have the building blocks of a game here, but we're still lacking player "
+"input.\n"
+"A game that plays itself isn't very much fun, so let's fix that."
+msgstr ""
+
+#: src/part2/input.md:6
+msgid ""
+"Paste this code below your `Main` loop.\n"
+"Like `Memcpy`, this is a function that can be reused from different places, "
+"using the `call` instruction."
+msgstr ""
+
+#: src/part2/input.md:9
+msgid ""
+"```rgbasm,linenos,start=110\n"
+"Input:\n"
+" ; Poll half the controller\n"
+" ld a, P1F_GET_BTN\n"
+" call .onenibble\n"
+" ld b, a ; B7-4 = 1; B3-0 = unpressed buttons"
+msgstr ""
+
+#: src/part2/input.md:16
+msgid ""
+" ; Poll the other half\n"
+" ld a, P1F_GET_DPAD\n"
+" call .onenibble\n"
+" swap a ; A3-0 = unpressed directions; A7-4 = 1\n"
+" xor a, b ; A = pressed buttons + directions\n"
+" ld b, a ; B = pressed buttons + directions"
+msgstr ""
+
+#: src/part2/input.md:23
+msgid ""
+" ; And release the controller\n"
+" ld a, P1F_GET_NONE\n"
+" ldh [rP1], a"
+msgstr ""
+
+#: src/part2/input.md:27
+msgid ""
+" ; Combine with previous wCurKeys to make wNewKeys\n"
+" ld a, [wCurKeys]\n"
+" xor a, b ; A = keys that changed state\n"
+" and a, b ; A = keys that changed to pressed\n"
+" ld [wNewKeys], a\n"
+" ld a, b\n"
+" ld [wCurKeys], a\n"
+" ret"
+msgstr ""
+
+#: src/part2/input.md:36
+msgid ""
+".onenibble\n"
+" ldh [rP1], a ; switch the key matrix\n"
+" call .knownret ; burn 10 cycles calling a known ret\n"
+" ldh a, [rP1] ; ignore value while waiting for the key matrix to settle\n"
+" ldh a, [rP1]\n"
+" ldh a, [rP1] ; this read counts\n"
+" or a, $F0 ; A7-4 = 1; A3-0 = unpressed keys\n"
+".knownret\n"
+" ret\n"
+"```"
+msgstr ""
+
+#: src/part2/input.md:47
+msgid ""
+"Unfortunately, reading input on the Game Boy is fairly involved (as you can "
+"see!), and it would be quite difficult to explain what this function does "
+"right now.\n"
+"So, I ask that you make an exception, and trust me that this function *does* "
+"read input.\n"
+"Alright? Good!"
+msgstr ""
+
+#: src/part2/input.md:51
+msgid ""
+"Now that we know how to use functions, let's call the `UpdateKeys` function "
+"in our main loop to read user input.\n"
+"`UpdateKeys` writes the held buttons to a location in memory that we called "
+"`wCurKeys`, which we can read from after the function returns.\n"
+"Because of this, we only need to call `UpdateKeys` once per frame."
+msgstr ""
+
+#: src/part2/input.md:55
+msgid ""
+"This is important, because not only is it faster to reload the inputs that "
+"we've already processed, but it also means that we will always act on the "
+"same inputs, even if the player presses or releases a button mid-frame."
+msgstr ""
+
+#: src/part2/input.md:57
+msgid ""
+"First, let's set aside some room for the two variables that `UpdateKeys` "
+"will use; paste this at the end of the `main.asm`:"
+msgstr ""
+
+#: src/part2/input.md:59
+msgid ""
+"```rgbasm,linenos,start=407\n"
+"SECTION \"Input Variables\", WRAM0\n"
+"wCurKeys: db\n"
+"wNewKeys: db\n"
+"```"
+msgstr ""
+
+#: src/part2/input.md:65
+msgid ""
+"Each variable must reside in RAM, and not ROM, because ROM is \"Read-"
+"Only\" (so you can't modify it).\n"
+"Additionally, each variable only needs to be one byte large, so we use `db` "
+"(\"Define Byte\") to reserve one byte of RAM for each."
+msgstr ""
+
+#: src/part2/input.md:68
+msgid ""
+"We're going to use the `and` opcode, which we can use to set the zero flag "
+"(`z`) to the value of the bit.\n"
+"We can use this along with the `PADF` constants in hardware.inc to read a "
+"particular key."
+msgstr ""
+
+#: src/part2/input.md:71
+msgid ""
+"```rgbasm,linenos,start=68\n"
+"Main:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp nc, Main\n"
+"WaitVBlank2:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp c, WaitVBlank2"
+msgstr ""
+
+#: src/part2/input.md:81
+msgid ""
+"\t; Check the current keys every frame and move left or right.\n"
+"\tcall Input"
+msgstr ""
+
+#: src/part2/input.md:84
+msgid ""
+"\t; First, check if the left button is pressed.\n"
+"CheckLeft:\n"
+"\tld a, [wCurKeys]\n"
+"\tand a, PADF_LEFT\n"
+"\tjp z, CheckRight\n"
+"Left:\n"
+"\t; Move the paddle one pixel to the left.\n"
+"\tld a, [_OAMRAM + 1]\n"
+"\tdec a\n"
+"\t; If we've already hit the edge of the playfield, don't move.\n"
+"\tcp a, 15\n"
+"\tjp z, Main\n"
+"\tld [_OAMRAM + 1], a\n"
+"\tjp Main"
+msgstr ""
+
+#: src/part2/input.md:99
+msgid ""
+"; Then check the right button.\n"
+"CheckRight:\n"
+"\tld a, [wCurKeys]\n"
+"\tand a, PADF_RIGHT\n"
+"\tjp z, Main\n"
+"Right:\n"
+"\t; Move the paddle one pixel to the right.\n"
+"\tld a, [_OAMRAM + 1]\n"
+"\tinc a\n"
+"\t; If we've already hit the edge of the playfield, don't move.\n"
+"\tcp a, 105\n"
+"\tjp z, Main\n"
+"\tld [_OAMRAM + 1], a\n"
+"\tjp Main\n"
+"```"
+msgstr ""
+
+#: src/part2/input.md:115
+msgid ""
+"Now, if you compile the project, you should be able to move the paddle left "
+"and right using the d-pad!!\n"
+"Hooray, we have the beginnings of a game!"
+msgstr ""
+
+#: src/part2/collision.md:1
+msgid "# Collision"
+msgstr ""
+
+#: src/part2/collision.md:3
+msgid ""
+"Being able to move around is great, but there's still one object we need for "
+"this game: a ball!\n"
+"Just like with the paddle, the first step is to create a tile for the ball "
+"and load it into VRAM."
+msgstr ""
+
+#: src/part2/collision.md:6
+msgid "## Graphics"
+msgstr ""
+
+#: src/part2/collision.md:8
+msgid ""
+"Add this to the bottom of your file along with the other graphics:\n"
+"```rgbasm,linenos,start=570\n"
+"Ball:\n"
+"\tdw `00033000\n"
+"\tdw `00322300\n"
+"\tdw `03222230\n"
+"\tdw `03222230\n"
+"\tdw `00322300\n"
+"\tdw `00033000\n"
+"\tdw `00000000\n"
+"\tdw `00000000\n"
+"BallEnd:\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:22
+msgid ""
+"Now copy it to VRAM somewhere in your initialization code, e.g. after "
+"copying the paddle's tile.\n"
+"```rgbasm,linenos,start=38\n"
+"\t; Copy the ball tile\n"
+"\tld de, Ball\n"
+"\tld hl, $8010\n"
+"\tld bc, BallEnd - Ball\n"
+"\tcall Memcopy\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:31
+msgid ""
+"In addition, we need to initialize an entry in OAM, following the code that "
+"initializes the paddle.\n"
+"```rgbasm,linenos,start=52\n"
+"\t; Initialize the paddle sprite in OAM\n"
+"\tld hl, _OAMRAM\n"
+"\tld a, 128 + 16\n"
+"\tld [hli], a\n"
+"\tld a, 16 + 8\n"
+"\tld [hli], a\n"
+"\tld a, 0\n"
+"\tld [hli], a\n"
+"\tld [hli], a\n"
+"\t; Now initialize the ball sprite\n"
+"\tld a, 100 + 16\n"
+"\tld [hli], a\n"
+"\tld a, 32 + 8\n"
+"\tld [hli], a\n"
+"\tld a, 1\n"
+"\tld [hli], a\n"
+"\tld a, 0\n"
+"\tld [hli], a\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:53
+msgid ""
+"As the ball bounces around the screen its momentum will change, sending it "
+"in different directions.\n"
+"Let's create two new variables to track the ball's momentum in each axis: "
+"`wBallMomentumX` and `wBallMomentumY`.\n"
+"```rgbasm,linenos,start=581\n"
+"SECTION \"Counter\", WRAM0\n"
+"wFrameCounter: db"
+msgstr ""
+
+#: src/part2/collision.md:59
+msgid ""
+"SECTION \"Input Variables\", WRAM0\n"
+"wCurKeys: db\n"
+"wNewKeys: db"
+msgstr ""
+
+#: src/part2/collision.md:63
+msgid ""
+"SECTION \"Ball Data\", WRAM0\n"
+"wBallMomentumX: db\n"
+"wBallMomentumY: db\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:68
+msgid ""
+"We will need to initialize these before entering the game loop, so let's do "
+"so right after we write the ball to OAM.\n"
+"By setting the X momentum to 1, and the Y momentum to -1, the ball will "
+"start out by going up and to the right.\n"
+"```rgbasm,linenos,start=61\n"
+"\t; Now initialize the ball sprite\n"
+"\tld a, 100 + 16\n"
+"\tld [hli], a\n"
+"\tld a, 32 + 8\n"
+"\tld [hli], a\n"
+"\tld a, 1\n"
+"\tld [hli], a\n"
+"\tld a, 0\n"
+"\tld [hli], a"
+msgstr ""
+
+#: src/part2/collision.md:81
+msgid ""
+"\t; The ball starts out going up and to the right\n"
+"\tld a, 1\n"
+"\tld [wBallMomentumX], a\n"
+"\tld a, -1\n"
+"\tld [wBallMomentumY], a\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:88
+msgid "## Prep work"
+msgstr ""
+
+#: src/part2/collision.md:90
+msgid ""
+"Now for the fun part!\n"
+"Add a bit of code at the beginning of your main loop that adds the momentum "
+"to the OAM positions.\n"
+"Notice that since this is the second OAM entry, we use `+ 4` for Y and `+ 5` "
+"for X.\n"
+"This can get pretty confusing, but luckily we only have two objects to keep "
+"track of.\n"
+"In the future, we'll go over a much easier way to use OAM.\n"
+"```rgbasm,linenos,start=90\n"
+"Main:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp nc, Main\n"
+"WaitVBlank2:\n"
+"\tld a, [rLY]\n"
+"\tcp 144\n"
+"\tjp c, WaitVBlank2"
+msgstr ""
+
+#: src/part2/collision.md:105
+msgid ""
+"\t; Add the ball's momentum to its position in OAM.\n"
+"\tld a, [wBallMomentumX]\n"
+"\tld b, a\n"
+"\tld a, [_OAMRAM + 5]\n"
+"\tadd a, b\n"
+"\tld [_OAMRAM + 5], a"
+msgstr ""
+
+#: src/part2/collision.md:112
+msgid ""
+"\tld a, [wBallMomentumY]\n"
+"\tld b, a\n"
+"\tld a, [_OAMRAM + 4]\n"
+"\tadd a, b\n"
+"\tld [_OAMRAM + 4], a\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:119
+msgid ""
+"You might want to compile your game again to see what this does.\n"
+"If you do, you should see the ball moving around, but it will just go "
+"through the walls and then fly offscreen."
+msgstr ""
+
+#: src/part2/collision.md:122
+msgid ""
+"To fix this, we need to add collision detection so that the ball can bounce "
+"around.\n"
+"We'll need to repeat the collision check a few times, so we're going to make "
+"use of two functions to do this."
+msgstr ""
+
+#: src/part2/collision.md:127
+msgid ""
+"Please do not get stuck on the details of this next function, as it uses "
+"some techniques and instructions we haven't discussed yet.\n"
+"The basic idea is that it converts the position of the sprite to a location "
+"on the tilemap.\n"
+"This way, we can check which tile our ball is touching so that we know when "
+"to bounce!"
+msgstr ""
+
+#: src/part2/collision.md:133
+msgid ""
+"```rgbasm,linenos,start=226\n"
+"; Convert a pixel position to a tilemap address\n"
+"; hl = $9800 + X + Y * 32\n"
+"; @param b: X\n"
+"; @param c: Y\n"
+"; @return hl: tile address\n"
+"GetTileByPixel:\n"
+"\t; First, we need to divide by 8 to convert a pixel position to a tile "
+"position.\n"
+"\t; After this we want to multiply the Y position by 32.\n"
+"\t; These operations effectively cancel out so we only need to mask the Y "
+"value.\n"
+"\tld a, c\n"
+"\tand a, %11111000\n"
+"\tld l, a\n"
+"\tld h, 0\n"
+"\t; Now we have the position * 8 in hl\n"
+"\tadd hl, hl ; position * 16\n"
+"\tadd hl, hl ; position * 32\n"
+"\t; Convert the X position to an offset.\n"
+"\tld a, b\n"
+"\tsrl a ; a / 2\n"
+"\tsrl a ; a / 4\n"
+"\tsrl a ; a / 8\n"
+"\t; Add the two offsets together.\n"
+"\tadd a, l\n"
+"\tld l, a\n"
+"\tadc a, h\n"
+"\tsub a, l\n"
+"\tld h, a\n"
+"\t; Add the offset to the tilemap's base address, and we are done!\n"
+"\tld bc, $9800\n"
+"\tadd hl, bc\n"
+"\tret\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:167
+msgid ""
+"The next function is called `IsWallTile`, and it's going to contain a list "
+"of tiles which the ball can bounce off of.\n"
+"```rgbasm,linenos,start=258\n"
+"; @param a: tile ID\n"
+"; @return z: set if a is a wall.\n"
+"IsWallTile:\n"
+"\tcp a, $00\n"
+"\tret z\n"
+"\tcp a, $01\n"
+"\tret z\n"
+"\tcp a, $02\n"
+"\tret z\n"
+"\tcp a, $04\n"
+"\tret z\n"
+"\tcp a, $05\n"
+"\tret z\n"
+"\tcp a, $06\n"
+"\tret z\n"
+"\tcp a, $07\n"
+"\tret\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:188
+msgid ""
+"This function might look a bit strange at first.\n"
+"Instead of returning its result in a *register*, like `a`, it returns it in "
+"[a *flag*](../part1/operations.md#flags): `Z`!\n"
+"If at any point a tile matches, the function has found a wall and exits with "
+"`Z` set.\n"
+"If the target tile ID (in `a`) matches one of the wall tile IDs, the "
+"corresponding `cp` will leave `Z` set; if so, we return immediately (via "
+"`ret z`), with `Z` set.\n"
+"But if we reach the last comparison and it still doesn't set `Z`, then we "
+"will know that we haven't hit a wall and don't need to bounce."
+msgstr ""
+
+#: src/part2/collision.md:194
+msgid "## Putting it together"
+msgstr ""
+
+#: src/part2/collision.md:196
+msgid ""
+"Time to use these new functions to add collision detection!\n"
+"Add the following after the code that updates the ball's position:\n"
+"```rgbasm,linenos,start=112\n"
+"BounceOnTop:\n"
+"\t; Remember to offset the OAM position!\n"
+"\t; (8, 16) in OAM coordinates is (0, 0) on the screen.\n"
+"\tld a, [_OAMRAM + 4]\n"
+"\tsub a, 16 + 1\n"
+"\tld c, a\n"
+"\tld a, [_OAMRAM + 5]\n"
+"\tsub a, 8\n"
+"\tld b, a\n"
+"\tcall GetTileByPixel ; Returns tile address in hl\n"
+"\tld a, [hl]\n"
+"\tcall IsWallTile\n"
+"\tjp nz, BounceOnRight\n"
+"\tld a, 1\n"
+"\tld [wBallMomentumY], a\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:216
+msgid ""
+"You'll see that when we load the sprite's positions, we subtract from them "
+"before calling `GetTileByPixel`.\n"
+"You might remember from the last chapter that OAM positions are slightly "
+"offset; that is, (0, 0) in OAM is actually completely offscreen.\n"
+"These `sub` instructions undo this offset."
+msgstr ""
+
+#: src/part2/collision.md:220
+msgid ""
+"However, there's a bit more to this: you might have noticed that we "
+"subtracted an extra pixel from the Y position.\n"
+"That's because (as the label suggests), this code is checking for a tile "
+"above the ball.\n"
+"We actually need to check *all four* sides of the ball so we know how to "
+"change the momentum according to which side collided, so... let's add the "
+"rest!"
+msgstr ""
+
+#: src/part2/collision.md:224
+msgid ""
+"```rgbasm,linenos,start=128\n"
+"BounceOnRight:\n"
+"\tld a, [_OAMRAM + 4]\n"
+"\tsub a, 16\n"
+"\tld c, a\n"
+"\tld a, [_OAMRAM + 5]\n"
+"\tsub a, 8 - 1\n"
+"\tld b, a\n"
+"\tcall GetTileByPixel\n"
+"\tld a, [hl]\n"
+"\tcall IsWallTile\n"
+"\tjp nz, BounceOnLeft\n"
+"\tld a, -1\n"
+"\tld [wBallMomentumX], a"
+msgstr ""
+
+#: src/part2/collision.md:239
+msgid ""
+"BounceOnLeft:\n"
+"\tld a, [_OAMRAM + 4]\n"
+"\tsub a, 16\n"
+"\tld c, a\n"
+"\tld a, [_OAMRAM + 5]\n"
+"\tsub a, 8 + 1\n"
+"\tld b, a\n"
+"\tcall GetTileByPixel\n"
+"\tld a, [hl]\n"
+"\tcall IsWallTile\n"
+"\tjp nz, BounceOnBottom\n"
+"\tld a, 1\n"
+"\tld [wBallMomentumX], a"
+msgstr ""
+
+#: src/part2/collision.md:253
+msgid ""
+"BounceOnBottom:\n"
+"\tld a, [_OAMRAM + 4]\n"
+"\tsub a, 16 - 1\n"
+"\tld c, a\n"
+"\tld a, [_OAMRAM + 5]\n"
+"\tsub a, 8\n"
+"\tld b, a\n"
+"\tcall GetTileByPixel\n"
+"\tld a, [hl]\n"
+"\tcall IsWallTile\n"
+"\tjp nz, BounceDone\n"
+"\tld a, -1\n"
+"\tld [wBallMomentumY], a\n"
+"BounceDone:\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:269
+msgid ""
+"That was a lot, but now the ball bounces around your screen!\n"
+"There's just one last thing to do before this chapter is over, and thats "
+"ball-to-paddle collision."
+msgstr ""
+
+#: src/part2/collision.md:272
+msgid "## Paddle bounce"
+msgstr ""
+
+#: src/part2/collision.md:274
+msgid ""
+"Unlike with the tilemap, there's no position conversions to do here, just "
+"straight comparisons.\n"
+"However, for these, we will need [the *carry* flag](../part1/operations."
+"md#flags).\n"
+"The carry flag is notated as `C`, like how the zero flag is notated as `Z`, "
+"but don't confuse it with the `c` register!"
+msgstr ""
+
+#: src/part2/collision.md:278
+msgid "::: tip A refresher on comparisons"
+msgstr ""
+
+#: src/part2/collision.md:280
+msgid ""
+"Just like `Z`, you can use the carry flag to jump conditionally.\n"
+"However, while `Z` is used to check if two numbers are equal, `C` can be "
+"used to check if a number is greater than or smaller than another one.\n"
+"For example, `cp a, b` sets `C` if `a < b`, and clears it if `a >= b`.\n"
+"(If you want to check `a <= b` or `a > b`, you can use `Z` and `C` in tandem "
+"with two `jp` instructions.)"
+msgstr ""
+
+#: src/part2/collision.md:287
+msgid ""
+"Armed with this knowledge, let's work through the paddle bounce code:\n"
+"```rgbasm,linenos,start=171\n"
+"\t; First, check if the ball is low enough to bounce off the paddle.\n"
+"\tld a, [_OAMRAM]\n"
+"\tld b, a\n"
+"\tld a, [_OAMRAM + 4]\n"
+"\tcp a, b\n"
+"\tjp nz, PaddleBounceDone ; If the ball isn't at the same Y position as the "
+"paddle, it can't bounce.\n"
+"\t; Now let's compare the X positions of the objects to see if they're "
+"touching.\n"
+"\tld a, [_OAMRAM + 5] ; Ball's X position.\n"
+"\tld b, a\n"
+"\tld a, [_OAMRAM + 1] ; Paddle's X position.\n"
+"\tsub a, 8\n"
+"\tcp a, b\n"
+"\tjp c, PaddleBounceDone\n"
+"\tadd a, 8 + 16 ; 8 to undo, 16 as the width.\n"
+"\tcp a, b\n"
+"\tjp nc, PaddleBounceDone"
+msgstr ""
+
+#: src/part2/collision.md:306
+msgid ""
+"\tld a, -1\n"
+"\tld [wBallMomentumY], a"
+msgstr ""
+
+#: src/part2/collision.md:309
+msgid ""
+"PaddleBounceDone:\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:312
+msgid ""
+"The Y position's check is simple, since our paddle is flat.\n"
+"However, the X position has two checks which widen the area the ball can "
+"bounce on.\n"
+"First we add 16 to the ball's position; if the ball is more than 16 pixels "
+"to the right of the paddle, it shouldn't bounce.\n"
+"Then we undo this by subtracting 16, and while we're at it, subtract another "
+"8 pixels; if the ball is more than 8 pixels to the left of the paddle, it "
+"shouldn't bounce."
+msgstr ""
+
+#: src/part2/collision.md:317
+msgid ""
+""
+msgstr ""
+
+#: src/part2/collision.md:406
+msgid "::: tip Paddle width"
+msgstr ""
+
+#: src/part2/collision.md:408
+msgid ""
+"You might be wondering why we checked 16 pixels to the right but only 8 "
+"pixels to the left.\n"
+"Remember that OAM positions represent the upper-*left* corner of a sprite, "
+"so the center of our paddle is actually 4 pixels to the right of the "
+"position in OAM.\n"
+"When you consider this, we're actually checking 12 pixels out on either side "
+"from the center of the paddle."
+msgstr ""
+
+#: src/part2/collision.md:412
+msgid ""
+"12 pixels might seem like a lot, but it gives some tolerance to the player "
+"in case their positioning is off.\n"
+"If you'd prefer to make this easier or more difficult, feel free to adjust "
+"the values!"
+msgstr ""
+
+#: src/part2/collision.md:417
+msgid "## BONUS: tweaking the bounce height"
+msgstr ""
+
+#: src/part2/collision.md:419
+msgid ""
+"You might notice that the ball seems to \"sink\" into the paddle a bit "
+"before bouncing. This is because the ball bounces when its top row of pixels "
+"aligns with the paddle's top row (see the image above). If you want, try to "
+"adjust this so that the ball bounces when its bottom row of pixels touches "
+"the paddle's top."
+msgstr ""
+
+#: src/part2/collision.md:421
+msgid "Hint: you can do this with just a single instruction!"
+msgstr ""
+
+#: src/part2/collision.md:423
+msgid "Answer:"
+msgstr ""
+
+#: src/part2/collision.md:425
+msgid ""
+"```diff linenos,start=171\n"
+"\tld a, [_OAMRAM]\n"
+"\tld b, a\n"
+"\tld a, [_OAMRAM + 4]\n"
+"+\tsub a, 6\n"
+"\tcp a, b\n"
+"```"
+msgstr ""
+
+#: src/part2/collision.md:433
+msgid "Alternatively, you can add `add a, 6` just after `ld a, [_OAMRAM]`."
+msgstr ""
+
+#: src/part2/collision.md:435
+msgid "In both cases, try playing with that `6` value; see what feels right!"
+msgstr ""
+
+#: src/part2/collision.md:437
+msgid ""
+msgstr ""
+
+#: src/part2/wip.md:1
+msgid "# Work in progress"
+msgstr ""
+
+#: src/part2/wip.md:3
+msgid "::: warning 🚧 🚧 🚧 🚧 🚧 🚧 🚧"
+msgstr ""
+
+#: src/part2/wip.md:5
+msgid ""
+"As explained in the initial tutorial presentation, Part Ⅱ consists of us "
+"building an *Arkanoid* game.\n"
+"However, this is not finished yet; lessons are uploaded as they are made, so "
+"the tutorial just abruptly stops at some point.\n"
+"Sorry!"
+msgstr ""
+
+#: src/part2/wip.md:9
+msgid ""
+"Please hold tight while I (and others!) are working on this, maybe [follow "
+"me on Twitter](https://twitter.com/issotm) for (infrequent) updates, and go "
+"to the next page to find out what you can do in the meantime!"
+msgstr ""
+
+#: src/part2/wip.md:11
+msgid "Thank you for your patience 😊 and see you around on GBDev!"
+msgstr ""
+
+#: src/next.md:1
+msgid "# Where to go next"
+msgstr ""
+
+#: src/next.md:3
+msgid ""
+"Oh.\n"
+"Well, you've reached the end of the tutorial...\n"
+"And yes, as you can see, it's not finished *yet*.\n"
+"I'm actively working on fixing that, though, please be a little patient :)"
+msgstr ""
+
+#: src/next.md:8
+msgid ""
+"In the meantime, the best course of action is to peruse the [resources]"
+"(resources.html) in the next section, and experiment by yourself.\n"
+"Well, given that, it may be a good idea to [ask around](help-feedback.html) "
+"for advice.\n"
+"A lot of the problems and questions you will be encountering have already "
+"been solved, so others can—and will!—help you getting started faster."
+msgstr ""
+
+#: src/resources.md:1
+msgid "# Resources"
+msgstr ""
+
+#: src/resources.md:3
+msgid "A.k.a. \"where to go from here\"."
+msgstr ""
+
+#: src/resources.md:5
+msgid "## Help channels"
+msgstr ""
+
+#: src/resources.md:7
+msgid ""
+"- [GBDev community home page](https://gbdev.io) and [chat channels](https://"
+"gbdev.io/chat)."
+msgstr ""
+
+#: src/resources.md:9
+msgid "## Other tutorials"
+msgstr ""
+
+#: src/resources.md:11
+msgid ""
+"- [evie's interrupts tutorial](https://eievui.ml/resources/interrupts) "
+"should help you understand how to use interrupts, and what they are useful "
+"for.\n"
+"- [tbsp's \"Simple GB ASM examples\"](https://github.com/tbsp/simple-gb-asm-"
+"examples) is a collection of ROMs, each built from a single, fairly short "
+"source file.\n"
+" If you found this tutorial too abstract and/or want to get your feet wet, "
+"this is a good place to go to!\n"
+"- [GB assembly by example](https://github.com/daid/gameboy-assembly-by-"
+"example), Daid's collection of code snippets.\n"
+" Consider this a continuation of the tutorial, but without explanations; "
+"it's still useful to peruse them and ask about it, they are overall good "
+"quality."
+msgstr ""
+
+#: src/resources.md:17
+msgid "## Complements"
+msgstr ""
+
+#: src/resources.md:19
+msgid ""
+"Did you enjoy the tutorial or one of the above?\n"
+"The following should prove useful along the rest of your journey!"
+msgstr ""
+
+#: src/resources.md:22
+msgid ""
+"- [RGBDS' online documentation](https://rgbds.gbdev.io/docs/) is always "
+"useful!\n"
+" Notably, you'll find [an instruction reference](https://rgbds.gbdev.io/"
+"docs/gbz80.7) and [the reference on RGBASM's syntax and features](https://"
+"rgbds.gbdev.io/docs/rgbasm.5).\n"
+"- [Pan Docs](https://gbdev.io/pandocs) are *the* reference for all Game Boy "
+"hardware.\n"
+" It's a good idea to consult it if you aare unsure how a register works, or "
+"if you're wondering how to do something.\n"
+"- [gb-optables](https://gbdev.io/gb-opcodes/optables) is a more compact "
+"instruction table, it becomes more useful when you stop needing the "
+"instructions' descriptions."
+msgstr ""
+
+#: src/thanks.md:1
+msgid "# Special Thanks"
+msgstr ""
+
+#: src/thanks.md:3
+msgid ""
+"Big thank you to [Twoflower/Triad](https://www.pouet.net/user.php?who=21982) "
+"for making the Hello World graphic."
+msgstr ""
+
+#: src/thanks.md:5
+msgid "I can't thank enough Chloé and many others for their continued support."
+msgstr ""
+
+#: src/thanks.md:7
+msgid "Thanks to the GBDev community for being so nice throughout the years."
+msgstr ""
+
+#: src/thanks.md:9
+msgid "**You are all great. Thank you so very much.**"
+msgstr ""
+
+#: src/thanks.md:13
+msgid ""
+"Thank you to the [Rust language](https://www.rust-lang.org) team for making "
+"[mdBook](https://github.com/rust-lang/mdBook), which powers this book (this "
+"honestly slick design is the stock one!!)"
+msgstr ""
+
+#: src/thanks.md:15
+msgid "Greets to AYCE, Phantasy, TPPDevs/RainbowDevs, Plutiedev, lft/kryo :)"
+msgstr ""
+
+#: src/thanks.md:17
+msgid ""
+"Shoutouts to [Eievui](https://eievui.ml), [Rangi](https://github.com/"
+"Rangi42), [MarkSixtyFour](https://github.com/MarkSixtyFour), [ax6](https://"
+"github.com/aaaaaa123456789), [Baŝto](https://github.com/basxto), [bbbbbr]"
+"(https://github.com/bbbbbr), and [bitnenfer](https://github.com/bitnenfer)!"
+msgstr ""
diff --git a/src/index.md b/src/index.md
index 35f19b80..5da8586e 100644
--- a/src/index.md
+++ b/src/index.md
@@ -18,6 +18,7 @@ There are some handy icons near the top of your screen!
- The "burger" toggles the navigation side panel;
- The brush allows selecting a different color theme;
- The magnifying glass pops up a search bar;
+- The world icon lets you change the language of the tutorial;
- The printer gives a single-page version of the *entire* tutorial, which you can print if you want;
- The GitHub icon links to the tutorial's source repository;
- The edit button allows you to suggest changes to the tutorial, provided that you have a GitHub account.
diff --git a/theme/css/general.css b/theme/css/general.css
index 4089d3e6..a87469fc 100644
--- a/theme/css/general.css
+++ b/theme/css/general.css
@@ -201,3 +201,12 @@ kbd > kbd {
.result-no-output {
font-style: italic;
}
+
+#language-list {
+ left: auto;
+ right: 10px;
+}
+
+#language-list a {
+ color: inherit;
+}
diff --git a/theme/index.hbs b/theme/index.hbs
new file mode 100644
index 00000000..78173495
--- /dev/null
+++ b/theme/index.hbs
@@ -0,0 +1,364 @@
+
+
+
+
+
+ {{ title }}
+ {{#if is_print }}
+
+ {{/if}}
+ {{#if base_url}}
+
+ {{/if}}
+
+
+ {{> head}}
+
+
+
+
+
+ {{#if favicon_svg}}
+
+ {{/if}}
+ {{#if favicon_png}}
+
+ {{/if}}
+
+
+
+ {{#if print_enable}}
+
+ {{/if}}
+
+
+
+ {{#if copy_fonts}}
+
+ {{/if}}
+
+
+
+
+
+
+
+ {{#each additional_css}}
+
+ {{/each}}
+
+ {{#if mathjax_support}}
+
+
+ {{/if}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+