Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
783cbdd
Use UTC for date/time data
shewitt-au Mar 8, 2026
0ce30ef
time_t is signed
shewitt-au Mar 9, 2026
bd45951
Hook up to selected language
shewitt-au Mar 9, 2026
78f8e3a
Stop language being "native"
shewitt-au Mar 9, 2026
163c010
Move non-windows code
shewitt-au Mar 9, 2026
9b171bc
Rename function
shewitt-au Mar 9, 2026
df9098d
POSIX formatting
shewitt-au Mar 9, 2026
8b2ffe7
Merge branch 'master' into F2592-Locale-date-time
shewitt-au Mar 9, 2026
6dff87f
More POSIX formatting
shewitt-au Mar 9, 2026
92df188
Start replacing bools with enums in APi
shewitt-au Mar 9, 2026
76e5763
Consolidate flags
shewitt-au Mar 10, 2026
e055e96
Rename some flags
shewitt-au Mar 10, 2026
6115858
Flags and stuff
shewitt-au Mar 10, 2026
b3a9a4a
It's not Invalid. We just can't format it!
shewitt-au Mar 10, 2026
5f2bdf2
Update inspector rows when lang changes
shewitt-au Mar 11, 2026
2ee43be
Update inspector rows when lang changes
shewitt-au Mar 11, 2026
1250285
Merge branch 'F2592-Locale-date-time' of https://github.com/shewitt-a…
shewitt-au Mar 11, 2026
6deef10
Remove some debugging code
shewitt-au Mar 11, 2026
e36c4da
Update libwolv
shewitt-au Mar 11, 2026
4daba23
Merge branch 'master' into F2592-Locale-date-time
shewitt-au Mar 11, 2026
3284999
Make tim32_t no just on Windows builds
shewitt-au Mar 12, 2026
e9ea35d
Windows/POSIX locale abstraction
shewitt-au Mar 12, 2026
8a9a594
Windows/POSIX locale abstraction
shewitt-au Mar 12, 2026
d6841de
Work on locale abstraction
shewitt-au Mar 13, 2026
718e484
Update libwolv
shewitt-au Mar 13, 2026
11367e0
Merge branch 'master' into F2592-Locale-date-time
shewitt-au Mar 14, 2026
d99d598
Changes from WSL build
shewitt-au Mar 14, 2026
637507b
Non-Windoes bug
shewitt-au Mar 14, 2026
6596c8c
Non-windows error
shewitt-au Mar 14, 2026
ad4503f
Merge branch 'F2592-Locale-date-time' of /mnt/c/projects/SteveImHex i…
shewitt-au Mar 14, 2026
9e0a148
Small code rearrangement
shewitt-au Mar 14, 2026
1c28476
Merge branch 'F2592-Locale-date-time' of /mnt/c/projects/SteveImHex i…
shewitt-au Mar 14, 2026
36d7759
DOS date and time
shewitt-au Mar 14, 2026
18bf1c4
DOS date and time for non-Windows
shewitt-au Mar 14, 2026
dd59b95
Update libwolv
shewitt-au Mar 14, 2026
0683279
Formatting
shewitt-au Mar 14, 2026
44c519d
Update libwolv
shewitt-au Mar 14, 2026
7a9f33f
Remove _lang UDL suffix
shewitt-au Mar 14, 2026
aeb6d07
Rename class
shewitt-au Mar 14, 2026
9692658
Update libwolv
shewitt-au Mar 14, 2026
ec649c2
Update libwolv
shewitt-au Mar 14, 2026
dc883e9
Update libwolv
shewitt-au Mar 15, 2026
c4557ed
Merge branch 'WerWolv:master' into F2592-Locale-date-time
shewitt-au Mar 15, 2026
a1eea1f
Merge branch 'WerWolv:master' into F2592-Locale-date-time
shewitt-au Mar 20, 2026
c928d12
Merge branch 'WerWolv:master' into F2592-Locale-date-time
shewitt-au Mar 21, 2026
b25e48c
Fix race condition
shewitt-au Mar 29, 2026
f7c38fb
More date/time formatting in pattern data
shewitt-au Mar 30, 2026
d29e067
Update libwolv & pattern_language
shewitt-au Mar 30, 2026
9b949db
Update libwolv
shewitt-au Apr 2, 2026
8ce3957
Fix Linux crash
shewitt-au Apr 2, 2026
a505a13
Fix linux crash
shewitt-au Apr 2, 2026
83b9954
Update libwolv
shewitt-au Apr 2, 2026
3a7fb81
DOS date & time
shewitt-au Apr 2, 2026
f071855
Fixed existing endian bug in DOS d&t
shewitt-au Apr 3, 2026
33f70ca
Update libwolv & pattern_language
shewitt-au Apr 3, 2026
47c1dac
Fix validation error
shewitt-au Apr 3, 2026
3df4de7
Start on user selected locale for d&t formatting
shewitt-au Apr 3, 2026
e209d04
User selected d&t: more
shewitt-au Apr 3, 2026
dd161bf
User selected d&t: Make locale combo follow lang combo
shewitt-au Apr 3, 2026
d59f13b
User selected d&t: More work
shewitt-au Apr 3, 2026
5efb1e6
User selected d&t: Fix native lang
shewitt-au Apr 3, 2026
63a6441
Clean up locale names
shewitt-au Apr 5, 2026
8bdf46c
Misc
shewitt-au Apr 5, 2026
2fc1d22
Long dates
shewitt-au Apr 5, 2026
4729575
Clean up
shewitt-au Apr 6, 2026
6238b43
DTfmt: Windows side - LocaleName class
shewitt-au Apr 6, 2026
ea6bb81
DTfmt: remone long-dates UI from Linux
shewitt-au Apr 7, 2026
f510315
DTfmt: update libwolv & pattern_language
shewitt-au Apr 7, 2026
56a7bc7
DTfmt: update libwolv
shewitt-au Apr 7, 2026
f71ef61
DTfmt: discord changes
shewitt-au Apr 8, 2026
349aa45
Remove a feature I was working on. Pehpahs I'll get back to it
shewitt-au Apr 27, 2026
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
6 changes: 3 additions & 3 deletions lib/libimhex/include/hex/api/content_registry/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,6 @@ EXPORT_MODULE namespace hex {
Widgets::Widget* add(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedSubCategory, const UnlocalizedString &unlocalizedName, std::unique_ptr<Widgets::Widget> &&widget);

void printSettingReadError(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json::exception &e);

void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value);
}

template<std::derived_from<Widgets::Widget> T>
Expand Down Expand Up @@ -316,6 +314,8 @@ EXPORT_MODULE namespace hex {
nlohmann::json m_value;
};

void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value);

template<typename T> requires (!(std::is_reference_v<T> || std::is_const_v<T>))
[[nodiscard]] T read(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, T defaultValue) {
auto setting = impl::getSetting(unlocalizedCategory, unlocalizedName, defaultValue);
Expand All @@ -337,7 +337,7 @@ EXPORT_MODULE namespace hex {
template<typename T> requires (!(std::is_reference_v<T> || std::is_const_v<T>))
void write(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, T value) {
impl::getSetting(unlocalizedCategory, unlocalizedName, value) = value;
impl::runOnChangeHandlers(unlocalizedCategory, unlocalizedName, value);
runOnChangeHandlers(unlocalizedCategory, unlocalizedName, value);

impl::store();
}
Expand Down
7 changes: 7 additions & 0 deletions lib/libimhex/include/hex/api/localization_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
#include <fmt/core.h>
#include <wolv/types/static_string.hpp>

// Forwards
namespace wolv::util {
class Locale;
}

EXPORT_MODULE namespace hex {

struct UnlocalizedString;
Expand All @@ -35,6 +40,8 @@ EXPORT_MODULE namespace hex {
void addLanguages(const std::string_view &languageList, std::function<std::string_view(const std::string &path)> callback);
void setLanguage(const LanguageId &languageId);
[[nodiscard]] const LanguageId& getSelectedLanguageId();
[[nodiscard]] wolv::util::Locale getSelectedLocale();
void setSelectedLocale(const LanguageId &languageId, bool longDate = false);
[[nodiscard]] const std::string& get(const LanguageId& languageId, const UnlocalizedString &unlocalizedString);
[[nodiscard]] const std::map<LanguageId, LanguageDefinition>& getLanguageDefinitions();
[[nodiscard]] const LanguageDefinition& getLanguageDefinition(const LanguageId &languageId);
Expand Down
38 changes: 24 additions & 14 deletions lib/libimhex/source/api/content_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,6 @@ namespace hex {
}
}

void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value) {
if (auto categoryIt = s_onChangeCallbacks->find(unlocalizedCategory); categoryIt != s_onChangeCallbacks->end()) {
if (auto nameIt = categoryIt->second.find(unlocalizedName); nameIt != categoryIt->second.end()) {
for (const auto &[id, callback] : nameIt->second) {
try {
callback(value);
} catch (const nlohmann::json::exception &e) {
log::error("Failed to run onChange handler for setting {}/{}: {}", unlocalizedCategory.get(), unlocalizedName.get(), e.what());
}
}
}
}
}

static AutoReset<nlohmann::json> s_settings;
const nlohmann::json& getSettingsData() {
return s_settings;
Expand Down Expand Up @@ -288,6 +274,20 @@ namespace hex {
category->unlocalizedDescription = unlocalizedDescription;
}

void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value) {
if (auto categoryIt = impl::s_onChangeCallbacks->find(unlocalizedCategory); categoryIt != impl::s_onChangeCallbacks->end()) {
if (auto nameIt = categoryIt->second.find(unlocalizedName); nameIt != categoryIt->second.end()) {
for (const auto &[id, callback] : nameIt->second) {
try {
callback(value);
} catch (const nlohmann::json::exception &e) {
log::error("Failed to run onChange handler for setting {}/{}: {}", unlocalizedCategory.get(), unlocalizedName.get(), e.what());
}
}
}
}
}

u64 onChange(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const OnChangeCallback &callback) {
static u64 id = 1;
(*impl::s_onChangeCallbacks)[unlocalizedCategory][unlocalizedName].emplace_back(id, callback);
Expand Down Expand Up @@ -698,6 +698,13 @@ namespace hex {
runtime.setOnCreateCallback([](prv::Provider *provider, pl::PatternLanguage &runtime) {
configureRuntime(runtime, provider);
});

// Handle language change
ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", [](const ContentRegistry::Settings::SettingsValue&) {
for (auto &pl : runtime.all()) {
pl.setLocale(hex::LocalizationManager::getSelectedLocale());
}
});
};

return *runtime;
Expand All @@ -712,6 +719,8 @@ namespace hex {
void configureRuntime(pl::PatternLanguage &runtime, prv::Provider *provider) {
runtime.reset();

runtime.setLocale(hex::LocalizationManager::getSelectedLocale());

if (provider != nullptr) {
runtime.setDataSource(provider->getBaseAddress(), provider->getActualSize(),
[provider](u64 offset, u8 *buffer, size_t size) {
Expand All @@ -724,6 +733,7 @@ namespace hex {
);
}


runtime.setIncludePaths(paths::PatternsInclude.read() | paths::Patterns.read());

for (const auto &[ns, name, paramCount, callback, dangerous] : impl::getFunctions()) {
Expand Down
20 changes: 19 additions & 1 deletion lib/libimhex/source/api/localization_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@
#include <nlohmann/json.hpp>

#include <mutex>
#include <shared_mutex>
#include <hex/helpers/debugging.hpp>
#include <wolv/utils/date_time_format.hpp>

namespace hex {

namespace LocalizationManager {

constexpr static auto FallbackLanguageId = "en-US";

namespace {
std::shared_mutex s_localeLock;
wolv::util::Locale s_locale;
}

namespace {

AutoReset<std::map<LanguageId, LanguageDefinition>> s_languageDefinitions;
Expand Down Expand Up @@ -133,7 +140,8 @@ namespace hex {
void setLanguage(const LanguageId &languageId) {
if (languageId == "native") {
setLanguage(hex::getOSLanguage().value_or(FallbackLanguageId));
s_selectedLanguageId = languageId;
// s_selectedLanguageId = languageId;
// /\-- Was this a bug, or have I made one?
return;
}

Expand All @@ -150,6 +158,16 @@ namespace hex {
return *s_selectedLanguageId;
}

[[nodiscard]] wolv::util::Locale getSelectedLocale() {
std::shared_lock lock(s_localeLock);
return s_locale;
}

void setSelectedLocale(const LanguageId &languageId, bool longDate) {
std::unique_lock lock(s_localeLock);
s_locale.set(languageId, longDate);
}

[[nodiscard]] const std::string& get(const LanguageId &languageId, const UnlocalizedString &unlocalizedString) {
static AutoReset<LanguageId> currentLanguageId;
static AutoReset<std::unordered_map<std::size_t, std::string>> loadedLocalization;
Expand Down
2 changes: 2 additions & 0 deletions plugins/builtin/romfs/lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@
"hex.builtin.setting.interface.fps.unlocked": "Unlocked",
"hex.builtin.setting.interface.fps.native": "Native",
"hex.builtin.setting.interface.language": "Language",
"hex.builtin.setting.interface.locale": "Locale",
"hex.builtin.setting.interface.longdate": "Use long dates",
"hex.builtin.setting.interface.multi_windows": "Enable Multi Window support",
"hex.builtin.setting.interface.randomize_window_title": "Use a randomized Window Title",
"hex.builtin.setting.interface.scaling_factor": "Scaling",
Expand Down
131 changes: 78 additions & 53 deletions plugins/builtin/source/content/data_inspector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@

#include <cstring>
#include <string>
#include <ctime>

#include <imgui_internal.h>
#include <fonts/vscode_icons.hpp>
#include <hex/helpers/default_paths.hpp>
#include <hex/helpers/encoding_file.hpp>
#include <hex/ui/imgui_imhex_extensions.h>
#include <popups/popup_file_chooser.hpp>
#include <wolv/utils/date_time_format.hpp>
#include <hex/api/localization_manager.hpp>

namespace hex::plugin::builtin {

Expand All @@ -33,6 +36,9 @@ namespace hex::plugin::builtin {
u8 data4[8];
};

const std::string s_invalid = "Invalid";
const std::string s_canNotFormat = "Can not format";

template<std::unsigned_integral T, size_t Size = sizeof(T)>
static ContentRegistry::DataInspector::impl::EditingFunction stringToUnsigned() requires(sizeof(T) <= sizeof(u64)) {
return ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
Expand Down Expand Up @@ -754,82 +760,56 @@ namespace hex::plugin::builtin {
};
});

#if defined(OS_WINDOWS)

ContentRegistry::DataInspector::add("hex.builtin.inspector.time32", sizeof(u32), [](auto buffer, auto endian, auto style) {
ContentRegistry::DataInspector::add("hex.builtin.inspector.time32", sizeof(i32), [](auto buffer, auto endian, auto style) {
std::ignore = style;

time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast<u32 *>(buffer.data()), endian);

std::string value;
try {
auto time = std::gmtime(&endianAdjustedTime);
if (time == nullptr) {
value = "Invalid";
} else {
value = fmt::format("{0:%a, %d.%m.%Y %H:%M:%S}", *time);
}
} catch (fmt::format_error &) {
value = "Invalid";
}

return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
});
time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast<i32 *>(buffer.data()), endian);

ContentRegistry::DataInspector::add("hex.builtin.inspector.time64", sizeof(u64), [](auto buffer, auto endian, auto style) {
std::ignore = style;

time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast<u64 *>(buffer.data()), endian);
const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale();

std::string value;
try {
auto time = std::gmtime(&endianAdjustedTime);
if (time == nullptr) {
value = "Invalid";
} else {
value = fmt::format("{0:%a, %d.%m.%Y %H:%M:%S}", *time);
}
} catch (fmt::format_error &) {
value = "Invalid";
using wolv::util::DTOpts;
auto optval = wolv::util::formatTT(lc, endianAdjustedTime, DTOpts::TT32 | DTOpts::DandT);
if (optval) {
value = optval.value();
}
else {
value = s_canNotFormat;
}

return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
});

#else

ContentRegistry::DataInspector::add("hex.builtin.inspector.time", sizeof(time_t), [](auto buffer, auto endian, auto style) {
std::ignore = style;

time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast<time_t *>(buffer.data()), endian);

const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale();

std::string value;
try {
auto time = std::gmtime(&endianAdjustedTime);
if (time == nullptr) {
value = "Invalid";
} else {
value = fmt::format("{0:%a, %d.%m.%Y %H:%M:%S}", *time);
}
} catch (const fmt::format_error &e) {
value = "Invalid";
using wolv::util::DTOpts;
auto optval = wolv::util::formatTT(lc, endianAdjustedTime, DTOpts::TT64 | DTOpts::DandT);
if (optval) {
value = optval.value();
}
else {
value = s_canNotFormat;
}

return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
});

#endif

struct DOSDate {
unsigned day : 5;
unsigned month : 4;
unsigned year : 7;
u16 day : 5;
u16 month : 4;
u16 year : 7;
};

struct DOSTime {
unsigned seconds : 5;
unsigned minutes : 6;
unsigned hours : 5;
u16 seconds : 5;
u16 minutes : 6;
u16 hours : 5;
};

ContentRegistry::DataInspector::add("hex.builtin.inspector.dos_date", sizeof(DOSDate), [](auto buffer, auto endian, auto style) {
Expand All @@ -839,7 +819,33 @@ namespace hex::plugin::builtin {
std::memcpy(&date, buffer.data(), sizeof(DOSDate));
date = hex::changeEndianness(date, endian);

auto value = fmt::format("{}/{}/{}", u8(date.day), u8(date.month), u8(date.year) + 1980);
bool ok = false;
std::string value;
if ( (date.day>=1 && date.day<=31) && (date.month>=1 && date.month<=12) ) {
std::tm tm{};
tm.tm_year = date.year + 80;
tm.tm_mon = date.month - 1;
tm.tm_mday = date.day;

#if defined(OS_WINDOWS)
time_t tt = _mkgmtime(&tm);
#else
time_t tt = timegm(&tm);
#endif
if (tt != -1) {
const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale();

using wolv::util::DTOpts;
auto optval = wolv::util::formatTT(lc, tt, DTOpts::TT64 | DTOpts::D);
if (optval) {
value = optval.value();
ok = true;
}
}
}
if (!ok) {
value = s_invalid;
}

return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
});
Expand All @@ -851,7 +857,26 @@ namespace hex::plugin::builtin {
std::memcpy(&time, buffer.data(), sizeof(DOSTime));
time = hex::changeEndianness(time, endian);

auto value = fmt::format("{:02}:{:02}:{:02}", u8(time.hours), u8(time.minutes), u8(time.seconds) * 2);
bool ok = false;
std::string value;
if ( (time.hours>=0 && time.hours<=23) &&
(time.minutes>=0 && time.minutes<=59) &&
(time.seconds>=0 && time.seconds<=29) )
{
time_t tt = time.hours*60*60 + time.minutes*60 + time.seconds*2;

const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale();

using wolv::util::DTOpts;
auto optval = wolv::util::formatTT(lc, tt, DTOpts::TT64 | DTOpts::T);
if (optval) {
value = optval.value();
ok = true;
}
}
if (!ok) {
value = s_invalid;
}

return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
});
Expand Down
Loading