Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ AGENTS.md
/save/
/soundtrack/

# The game's message files exported as JSON (darkcloud --export-text, docs/LOCALIZATION.md): the
# disc's own text, never committed.
/lang-export/

# Flatpak builds (docs/FLATPAK.md)
/.flatpak-builder/
/build-dir/
Expand Down
292 changes: 292 additions & 0 deletions docs/LOCALIZATION.md

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions docs/PC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1197,8 +1197,9 @@ characters in it, and its own accents only.

`GameTextEncode` turns UTF-8 into codes. Curly quotes and dashes become the
font's own; any other character the font lacks, and any byte that is not
UTF-8, becomes `?` and is counted. `{N}` writes code N as it is, for control
codes and icons, and `{{` a `{`; `GameTextDecode` writes the same form, so
UTF-8, becomes `?` and is counted. A control code or icon is written in braces by its name (`{page}`, `{cyan}`, `{L1}`,
`{value3}`; the list is in LOCALIZATION.md) or by its number, `{N}`, and `{{` is a `{`;
`GameTextDecode` writes the name where there is one, else the number, so
any message reads back and encodes to the same codes. `GameTextFile` lays
texts out as a message file for `ClsMes::SetBuff`. `GameText` is one piece of
text drawn as the menus draw their help line (`InitMenuMesSet`'s
Expand All @@ -1207,7 +1208,7 @@ the text, `SetColour` a `FontColor`, then `Draw(x, y, alpha)` from a menu's
draw function; `Width` gives its width for aligning a value.

Both have retail's fixed sizes. A window lays out at most `MES_WIN_LINE_MAX`
(720) characters, counting the end and what a `{N}` name, value or system
(720) characters, counting the end and what a `{name1}`, `{value}` or `{insert1}` name, value or system
message code expands to; retail's `SetMesWinTbl` writes on past the table, so
the port's (`port/src/clsmes.cpp`) takes no more, and `GameText::Set` gives back
-1, draws nothing and has a width of 0 for a text that did not fit. A line past
Expand All @@ -1217,6 +1218,9 @@ without one does. A message file is s16 throughout: an id is -0x8000 to
the last message by id may extend beyond that range. `GameTextFile::Set`
gives back -1 and leaves the file as it was where either would not hold.

The text of retail's message files can be replaced from JSON, one file per language, with the
disc's text as the fallback: see [LOCALIZATION.md](LOCALIZATION.md).

## The Options screen

The game's Options screen, from the title, the town menu and the dungeon menu,
Expand Down
16 changes: 14 additions & 2 deletions port/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ else()
file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_vorbis.c
${AUDIO_DECODER_DIR}/stb_vorbis.c EXPECTED_HASH SHA256=4c7cb2ff1f7011e9d67950446b7eb9ca044f2e464d76bfbb0b84dd2e23e65636)
endif()
# The message font's rasterizer (platform/ttffont.cpp): a single-file library, public domain or MIT, fetched at a pinned commit.
set(TTF_DIR ${CMAKE_BINARY_DIR}/stb_truetype)
file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h
${TTF_DIR}/stb_truetype.h EXPECTED_HASH SHA256=ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab)

set(PS2_DIR ${CMAKE_SOURCE_DIR}/ps2/src)
set(PORT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
Expand Down Expand Up @@ -213,12 +217,12 @@ file(CONFIGURE OUTPUT ${SHADER_HEADER_DIR}/options/credits.hpp
CONTENT "#pragma once\ninline constexpr const char *kCredits[] = {${credit_names}};\n")

add_library(dc_host OBJECT ${HOST_SOURCES} ${SHADER_HEADERS})
target_include_directories(dc_host PRIVATE ${PORT_DIR} ${SHADER_HEADER_DIR} ${AUDIO_DECODER_DIR})
target_include_directories(dc_host PRIVATE ${PORT_DIR} ${SHADER_HEADER_DIR} ${AUDIO_DECODER_DIR} ${TTF_DIR})
target_link_libraries(dc_host PRIVATE SDL3::SDL3 Vulkan::Vulkan nlohmann_json::nlohmann_json)
target_compile_options(dc_host PRIVATE -Wall -ffunction-sections -fdata-sections)

add_library(dc_port OBJECT ${PORT_LIB_SOURCES})
target_link_libraries(dc_port PRIVATE dc_options SDL3::SDL3 Vulkan::Vulkan)
target_link_libraries(dc_port PRIVATE dc_options SDL3::SDL3 Vulkan::Vulkan nlohmann_json::nlohmann_json)
# Replacement units reach the host side as "gfx/gfx.hpp", "platform/window.hpp".
target_include_directories(dc_port PRIVATE ${PORT_DIR} ${SHADER_HEADER_DIR})
# The game's headers trip a few warnings that only describe how MWCC's source
Expand Down Expand Up @@ -269,6 +273,14 @@ endif()
include(GNUInstallDirs)
install(TARGETS darkcloud RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

# The strings the port draws itself, translated (docs/LOCALIZATION.md): lang/ beside the executable, where the
# game reads its language files.
add_custom_target(dc_lang
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/lang $<TARGET_FILE_DIR:darkcloud>/lang
COMMENT "Copying the port's translated strings")
add_dependencies(darkcloud dc_lang)
install(DIRECTORY lang/ DESTINATION ${CMAKE_INSTALL_BINDIR}/lang)

# The tests use an installed GoogleTest, or fetch one at configure time; a build with neither turns them off.
option(DC_BUILD_TESTS "Build darkcloud_tests" ON)
if(DC_BUILD_TESTS)
Expand Down
162 changes: 162 additions & 0 deletions port/lang/de_de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
"options.shortcuts": "{square} Standard\n{triangle} Verwerfen\n{circle} Verlassen",
"options.help.save_failed": "Speichern misslang.\nÄnderungen gelten.\nVerlassen: erneut\nconfig.json schreiben.",
"options.help.display_kept": "Die Anzeige konnte\nnicht wechseln und\nbleibt im alten Modus.\nAnderen Modus wählen.",
"options.help.exit": "{cross} Verlassen\n{square} Seitenstandard\n{triangle} Alles verwerfen\n{circle} Verlassen (Zeile)",
"options.help.display_now": "Die Anzeige konnte\nnicht wechseln. Sie ist\njetzt %1",
"options.display.fullscreen_now": "Vollbild.",
"options.display.window_now": "ein Fenster\nvon %1 x %2.",
"options.page.game": "Spiel",
"options.game.save_cursor_position.label": "Cursor merken",
"options.game.save_cursor_position.choice.0": "An",
"options.game.save_cursor_position.choice.1": "Aus",
"options.game.message_speed.label": "Textgeschwindigkeit",
"options.game.message_speed.choice.0": "Normal",
"options.game.message_speed.choice.1": "Schnell",
"options.game.clock.label": "Uhr",
"options.game.clock.choice.0": "An",
"options.game.clock.choice.1": "Aus",
"options.game.time_speed.label": "Zeitgeschwindigkeit",
"options.game.time_speed.choice.0": "Normal",
"options.game.time_speed.choice.1": "Schnell",
"options.game.map.label": "Dungeon-Karte",
"options.game.map.choice.0": "1",
"options.game.map.choice.1": "2",
"options.game.map.choice.2": "3",
"options.game.map.choice.3": "Aus",
"options.game.enemy_damage.label": "Gegnerschaden",
"options.game.enemy_damage.choice.0": "An",
"options.game.enemy_damage.choice.1": "Aus",
"options.game.player_damage.label": "Gruppenschaden",
"options.game.player_damage.choice.0": "An",
"options.game.player_damage.choice.1": "Aus",
"options.game.enemy_hp.label": "Gegner-LP",
"options.game.enemy_hp.choice.0": "An",
"options.game.enemy_hp.choice.1": "Aus",
"options.game.names.label": "Namen",
"options.game.names.choice.0": "An",
"options.game.names.choice.1": "Aus",
"options.discord.rich_presence.label": "Discord aktivieren",
"options.discord.rich_presence.help": "\"Discord Rich Presence\"\nZeigt auf Discord,\nwas du spielst.",
"options.discord.rich_presence.choice.0": "An",
"options.discord.rich_presence.choice.1": "Aus",
"options.game.language.label": "Sprache",
"options.game.language.help": "\"Sprache\"\nSprache des Spiels.\nFragen: Auswahl beim\nStart anzeigen.",
"options.game.language.choice.0": "Fragen",
"options.game.language.choice.1": "English",
"options.game.language.choice.2": "Français",
"options.game.language.choice.3": "Deutsch",
"options.game.language.choice.4": "Italiano",
"options.game.language.choice.5": "Español",
"options.page.display": "Anzeige",
"options.video.fullscreen.label": "Fenstermodus",
"options.video.fullscreen.help": "\"Fenstermodus\"\nIm Fenster oder im\nVollbild spielen.",
"options.video.fullscreen.choice.0": "Fenster",
"options.video.fullscreen.choice.1": "Vollbild",
"options.video.width.label": "Auflösung",
"options.video.width.help": "\"Auflösung\"\nGröße des Fensters.\nVollbild und Desktop\nnutzen den Monitor.",
"options.video.present_mode.label": "V-Sync",
"options.video.present_mode.help": "\"V-Sync\"\nAn: kein Tearing.\nSchnell: weniger\nLatenz. Aus: Tearing.",
"options.video.present_mode.choice.0": "An",
"options.video.present_mode.choice.1": "Schnell",
"options.video.present_mode.choice.2": "Aus",
"options.video.max_fps.label": "Bildratenlimit",
"options.video.max_fps.help": "\"Bildratenlimit\"\nDie meisten Bilder\npro Sekunde.",
"options.video.aspect.label": "Seitenverhältnis",
"options.video.aspect.help": "\"Seitenverhältnis\"\nBreit: füllt das\nFenster. 4:3: das Bild\nder PS2.",
"options.video.aspect.choice.0": "Breit",
"options.video.aspect.choice.1": "4:3",
"options.video.ui_scale.label": "Menügröße",
"options.video.ui_scale.help": "\"Menügröße\"\nGröße von HUD und\nMenüs, nach dem\nVerlassen der Optionen.",
"options.video.interpolation.label": "Weiche Bewegung",
"options.video.interpolation.help": "\"Weiche Bewegung\"\nZeichnet Bilder\nzwischen den Schritten.",
"options.video.interpolation.choice.0": "An",
"options.video.interpolation.choice.1": "Aus",
"options.video.show_fps.label": "FPS-Anzeige",
"options.video.show_fps.help": "\"FPS-Anzeige\"\nZeigt die Bildrate\nin der Ecke.",
"options.video.show_fps.choice.0": "An",
"options.video.show_fps.choice.1": "Aus",
"options.video.soft_focus.label": "Weichzeichner",
"options.video.soft_focus.choice.0": "An",
"options.video.soft_focus.choice.1": "Aus",
"options.page.audio": "Audio",
"options.audio.master_volume.label": "Lautstärke",
"options.audio.master_volume.help": "\"Lautstärke\"\nWie laut das Spiel ist.",
"options.audio.sound.label": "Ton",
"options.audio.sound.choice.0": "Stereo",
"options.audio.sound.choice.1": "Mono",
"options.page.controls": "Steuerung",
"options.input.vibration.label": "Vibration",
"options.input.vibration.choice.0": "An",
"options.input.vibration.choice.1": "Aus",
"options.input.mouse_sensitivity.label": "Mausempfindlichkeit",
"options.input.mouse_sensitivity.help": "\"Mausempfindlichkeit\"\nWie schnell die Maus\ndie Kamera dreht.",
"options.input.mouse_invert_y.label": "Maus Y umkehren",
"options.input.mouse_invert_y.help": "\"Maus Y umkehren\"\nMaus nach oben:\nBlick nach unten.",
"options.input.mouse_invert_y.choice.0": "An",
"options.input.mouse_invert_y.choice.1": "Aus",
"options.input.stick_sensitivity.label": "Stick-Empfindlichk.",
"options.input.stick_sensitivity.help": "\"Stick-Empfindlichk.\"\nWie weit der Stick\nkippen muss.",
"options.input.stick_invert_x.label": "Stick X umkehren",
"options.input.stick_invert_x.help": "\"Stick X umkehren\"\nKehrt Kamera links\nund rechts um.",
"options.input.stick_invert_x.choice.0": "An",
"options.input.stick_invert_x.choice.1": "Aus",
"options.input.stick_invert_y.label": "Stick Y umkehren",
"options.input.stick_invert_y.help": "\"Stick Y umkehren\"\nKehrt Kamera oben\nund unten um.",
"options.input.stick_invert_y.choice.0": "An",
"options.input.stick_invert_y.choice.1": "Aus",
"options.input.gyro.label": "Gyro",
"options.input.gyro.help": "\"Gyro\"\nWenn das Neigen des\nPads die Kamera dreht.",
"options.input.gyro.choice.0": "Aus",
"options.input.gyro.choice.1": "Immer",
"options.input.gyro.choice.2": "Ego-Sicht",
"options.input.gyro.choice.3": "Beim Halten",
"options.input.gyro_sensitivity.label": "Gyro-Empfindlichk.",
"options.input.gyro_sensitivity.help": "\"Gyro-Empfindlichk.\"\nWie schnell Neigen\ndie Kamera dreht.",
"options.input.gyro_invert_x.label": "Gyro X umkehren",
"options.input.gyro_invert_x.help": "\"Gyro X umkehren\"\nKehrt Gyro links\nund rechts um.",
"options.input.gyro_invert_x.choice.0": "An",
"options.input.gyro_invert_x.choice.1": "Aus",
"options.input.gyro_invert_y.label": "Gyro Y umkehren",
"options.input.gyro_invert_y.help": "\"Gyro Y umkehren\"\nKehrt Gyro oben\nund unten um.",
"options.input.gyro_invert_y.choice.0": "An",
"options.input.gyro_invert_y.choice.1": "Aus",
"options.video.width.value.Desktop": "Desktop",
"options.video.max_fps.value.Unlimited": "Unbegrenzt",
"options.game.qte_always_win.label": "QTEs immer gewinnen",
"options.game.qte_always_win.help": "\"QTEs immer gewinnen\"\nTastenabfragen enden\nimmer perfekt.",
"options.game.qte_always_win.choice.0": "An",
"options.game.qte_always_win.choice.1": "Aus",
"options.video.fps_detail.label": "FPS-Info",
"options.video.fps_detail.help": "\"FPS-Info\"\nWas der Zähler zeigt:\nnur FPS, mit Ticks\noder alles.",
"options.video.fps_detail.choice.0": "FPS",
"options.video.fps_detail.choice.1": "FPS+Ticks",
"options.video.fps_detail.choice.2": "Alles",
"options.input.mouse_zoom.label": "Mausrad-Zoom",
"options.input.mouse_zoom.help": "\"Mausrad-Zoom\"\nScrollen, um näher\noder weiter weg von der\nFigur zu gehen.",
"options.input.mouse_zoom.choice.0": "An",
"options.input.mouse_zoom.choice.1": "Aus",
"options.input.bindings.zoom_reset.label": "Zoom zurücksetzen",
"options.input.bindings.zoom_reset.help": "\"Zoom zurücksetzen\"\nStellt normalen\nKameraabstand her.",
"options.help.turn_page": "{L1} {R1} blättern.",
"options.page.game.help": "\"Spiel\"\nWie das Spiel abläuft.",
"options.page.display.help": "\"Anzeige\"\nFenster und Bild.",
"options.page.audio.help": "\"Audio\"\nTon und Musik.",
"options.page.controls.help": "\"Steuerung\"\nMaus, Gamepad und Gyro.",
"options.page.accessibility": "Bedienhilfen",
"options.page.accessibility.help": "\"Bedienhilfen\"\nHilfe bei den schwereren\nStellen des Spiels.",
"options.audio.soundtrack.label": "Soundtrack",
"options.audio.soundtrack.help": "\"Soundtrack\"\nPS2: die Musik des Spiels.\nEigen: Ihre eigenen\nAufnahmen, ab dem\nnächsten Lied.",
"options.audio.soundtrack.choice.0": "PS2",
"options.audio.soundtrack.choice.1": "Eigen",
"options.audio.surround.label": "Surround",
"options.audio.surround.help": "\"Surround\"\nVerteilt den Ton auf\n5.1-Lautsprecher.",
"options.audio.surround.choice.0": "An",
"options.audio.surround.choice.1": "Aus",
"options.input.mouse_camera_return.label": "Vertikale Rückkehr",
"options.input.mouse_camera_return.help": "\"Vertikale Rückkehr\"\nWie schnell die Kamera\nbei ruhender Maus auf\nnormale Höhe zurückkehrt.",
"options.video.text_shadow.label": "Textschatten",
"options.video.text_shadow.help": "\"Textschatten\"\nWie dunkel der Schatten\nunter dem Text ist (bei\nTrueType-Schrift).",
"options.video.text_shadow.choice.0": "Weich",
"options.video.text_shadow.choice.1": "Tief"
}
Loading
Loading