diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 87f4099..6ef5f59 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -5,7 +5,11 @@ set(DUSK_GAME_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL ${DUSK_CACHE_TARGET}) -add_subdirectory(palette)# Palette asset needs to be added before any images. +# Palette asset needs to be added before any images. +add_subdirectory(palette) + +# Languages need to be added before anything that uses text. +add_subdirectory(locale) add_subdirectory(entity) add_subdirectory(map) diff --git a/assets/locale/CMakeLists.txt b/assets/locale/CMakeLists.txt new file mode 100644 index 0000000..49e7742 --- /dev/null +++ b/assets/locale/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_asset(LANGUAGE en_US.po) \ No newline at end of file diff --git a/assets/locale/en_US.po b/assets/locale/en_US.po new file mode 100644 index 0000000..279fa32 --- /dev/null +++ b/assets/locale/en_US.po @@ -0,0 +1,10 @@ +msgid "" +msgstr "" +"Language: us\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" + +msgid "test.test" +msgstr "Hello this is a test." \ No newline at end of file diff --git a/src/asset/assettype.h b/src/asset/assettype.h index dfccc10..dd9026d 100644 --- a/src/asset/assettype.h +++ b/src/asset/assettype.h @@ -16,9 +16,14 @@ typedef enum { ASSET_TYPE_COUNT, } assettype_t; +typedef enum { + ASSET_LOAD_STRAT_ENTIRE, +} assetloadstrat_t; + typedef struct { const char_t *header; const size_t dataSize; + const assetloadstrat_t loadStrategy; errorret_t (*load)(void *data, void *output); } assettypedef_t; @@ -29,12 +34,14 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = { [ASSET_TYPE_PALETTE_IMAGE] = { .header = "DPI", + .loadStrategy = ASSET_LOAD_STRAT_ENTIRE, .dataSize = sizeof(assetpaletteimage_t), .load = assetPaletteImageLoad }, [ASSET_TYPE_ALPHA_IMAGE] = { .header = "DAI", + .loadStrategy = ASSET_LOAD_STRAT_ENTIRE, .dataSize = sizeof(assetalphaimage_t), .load = assetAlphaImageLoad }, diff --git a/src/asset/type/assetlanguage.h b/src/asset/type/assetlanguage.h new file mode 100644 index 0000000..63bc9e3 --- /dev/null +++ b/src/asset/type/assetlanguage.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "locale/language/keys.h" + +#pragma pack(push, 1) +typedef struct { + uint32_t chunk; + uint32_t offset; + uint32_t length; +} assetlanguagestring_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct { + assetlanguagestring_t strings[LANG_KEY_COUNT]; +} assetlanguageheader_t; +#pragma pack(pop) \ No newline at end of file diff --git a/src/locale/language/languagechunk.h b/src/locale/language/languagechunk.h new file mode 100644 index 0000000..7a3d77c --- /dev/null +++ b/src/locale/language/languagechunk.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +#define LANG_CHUNK_STRING_COUNT 64 +#define LANG_CHUNK_STRING_MAX_LENGTH DUSK_LANG_STRING_MAX_LENGTH + +#pragma pack(push, 1) +typedef struct { + char_t +} languagechunk_t; +#pragma pack(pop) \ No newline at end of file diff --git a/src/util/string.c b/src/util/string.c index 76be012..f06b116 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -74,7 +74,7 @@ int32_t stringFormatVA( char_t *dest, const size_t destSize, const char_t *format, - const va_list args + va_list args ) { assertNotNull(format, "format must not be NULL"); diff --git a/src/util/string.h b/src/util/string.h index 189e5a6..fda8d95 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -97,7 +97,7 @@ int32_t stringFormatVA( char_t *dest, const size_t destSize, const char_t *format, - const va_list args + va_list args ); /** diff --git a/tools/assetstool/main.py b/tools/assetstool/main.py index 6a37698..ec96699 100644 --- a/tools/assetstool/main.py +++ b/tools/assetstool/main.py @@ -3,6 +3,7 @@ from args import inputAssets, args from processasset import processAsset from processpalette import processPaletteList from processtileset import processTilesetList +from processlanguage import processLanguageList from assethelpers import getBuiltAssetsRelativePath import zipfile @@ -31,6 +32,7 @@ with zipfile.ZipFile(outputFileName, 'w') as zipf: # Generate additional headers. processPaletteList() processTilesetList() +processLanguageList() # Finalize build if args.build_type == 'header': diff --git a/tools/assetstool/processasset.py b/tools/assetstool/processasset.py index e11c88a..0ae2ee9 100644 --- a/tools/assetstool/processasset.py +++ b/tools/assetstool/processasset.py @@ -4,6 +4,7 @@ from processimage import processImage from processpalette import processPalette from processtileset import processTileset from processmap import processMap +from processlanguage import processLanguage processedAssets = [] @@ -22,6 +23,8 @@ def processAsset(asset): return processTileset(asset) elif t == 'map': return processMap(asset) + elif t == 'language': + return processLanguage(asset) else: print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'") sys.exit(1) \ No newline at end of file diff --git a/tools/assetstool/processlanguage.py b/tools/assetstool/processlanguage.py new file mode 100644 index 0000000..32dd2e9 --- /dev/null +++ b/tools/assetstool/processlanguage.py @@ -0,0 +1,73 @@ +import sys +import os +from args import args +from assetcache import assetCache, assetGetCache +from assethelpers import getAssetRelativePath +import polib +import re + +LANGUAGE_DATA = {} +LANGUAGE_KEYS = [] + +def processLanguageList(): + # Language keys header data + headerKeys = "// Auto-generated language keys header file.\n" + headerKeys += "#pragma once\n" + headerKeys += "#include \"dusk.h\"\n\n" + + keyIndex = 0 + for key in LANGUAGE_KEYS: + headerKeys += f"#define {getLanguageVariableName(key)} {keyIndex}\n" + keyIndex += 1 + + for lang in LANGUAGE_DATA: + if key not in LANGUAGE_DATA[lang]: + print(f"Warning: Missing translation for key '{key}' in language '{lang}'") + sys.exit(1) + + headerKeys += f"\n#define LANG_KEY_COUNT {len(LANGUAGE_KEYS)}\n" + + # Write out the language keys header file + outputFile = os.path.join(args.headers_dir, "locale", "language", "keys.h") + os.makedirs(os.path.dirname(outputFile), exist_ok=True) + with open(outputFile, "w") as f: + f.write(headerKeys) + +def getLanguageVariableName(languageKey): + # Take the language key, prepend LANG_, uppercase, replace any non symbols + # with _ + key = languageKey.strip().upper() + key = re.sub(r'[^A-Z0-9]', '_', key) + return f"LANG_{key}" + +def processLanguage(asset): + cache = assetGetCache(asset['path']) + if cache is not None: + return cache + + # Load PO File + po = polib.pofile(asset['path']) + + langName = po.metadata.get('Language') + if langName not in LANGUAGE_DATA: + LANGUAGE_DATA[langName] = {} + + for entry in po: + key = entry.msgid + val = entry.msgstr + + if key not in LANGUAGE_KEYS: + LANGUAGE_KEYS.append(key) + + if key not in LANGUAGE_DATA[langName]: + LANGUAGE_DATA[langName][key] = val + else: + print(f"Error: Duplicate translation key '{key}' in language '{langName}'") + sys.exit(1) + + outLanguageData = { + 'data': po, + 'path': asset['path'], + 'files': [] + } + return assetCache(asset['path'], outLanguageData) \ No newline at end of file