Files
AnyPS5/core/libs/prx/libSceSystemService/LanguageLocale.hpp
T
MarcZX10 f2fbb65aaa feat(libSceSystemService): parse a locale given as a BCP-47 tag or a POSIX name
ParseLocale normalises the text as a POSIX name first, which leaves a BCP-47
tag unchanged except for underscores, and then parses the result, so the
caller handles en-US and en_US.UTF-8 alike. C, POSIX and an empty text report
no locale and leave the output tag as it was.
2026-10-29 08:51:16 +00:00

109 lines
4.2 KiB
C++

#ifndef CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_LANGUAGELOCALE_HPP
#define CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_LANGUAGELOCALE_HPP
#include <cstddef>
#include <string>
#include <string_view>
#include <utility>
namespace SystemServiceLocale {
struct Tag {
std::string language;
std::string script;
std::string region;
};
namespace Detail {
constexpr bool IsAlpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; }
constexpr char Lower(char c) { return c >= 'A' && c <= 'Z' ? static_cast<char>(c - 'A' + 'a') : c; }
constexpr char Upper(char c) { return c >= 'a' && c <= 'z' ? static_cast<char>(c - 'a' + 'A') : c; }
constexpr bool AllOf(std::string_view text, bool (*predicate)(char)) {
for (char c : text) {
if (!predicate(c)) return false;
}
return true;
}
constexpr bool IsAlphanumeric(char c) { return IsAlpha(c) || IsDigit(c); }
constexpr bool EqualsIgnoringCase(std::string_view left, std::string_view right) {
if (left.size() != right.size()) return false;
for (std::size_t i = 0; i < left.size(); ++i) {
if (Lower(left[i]) != Lower(right[i])) return false;
}
return true;
}
}
inline bool ParseTag(std::string_view text, Tag& tag) {
Tag parsed;
std::size_t index = 0;
bool seenScript = false;
bool seenRegion = false;
bool tail = false;
std::size_t position = 0;
while (true) {
const std::size_t end = text.find('-', position);
const std::string_view subtag = text.substr(position, end == std::string_view::npos ? std::string_view::npos : end - position);
if (subtag.empty() || subtag.size() > 8 || !Detail::AllOf(subtag, Detail::IsAlphanumeric)) return false;
if (index == 0) {
if ((subtag.size() != 2 && subtag.size() != 3) || !Detail::AllOf(subtag, Detail::IsAlpha)) return false;
for (char c : subtag) parsed.language += Detail::Lower(c);
} else if (!tail) {
if (subtag.size() == 1) {
tail = true;
} else if (!seenScript && !seenRegion && subtag.size() == 4 && Detail::AllOf(subtag, Detail::IsAlpha)) {
seenScript = true;
parsed.script += Detail::Upper(subtag[0]);
for (std::size_t i = 1; i < subtag.size(); ++i) parsed.script += Detail::Lower(subtag[i]);
} else if (!seenRegion && ((subtag.size() == 2 && Detail::AllOf(subtag, Detail::IsAlpha)) || (subtag.size() == 3 && Detail::AllOf(subtag, Detail::IsDigit)))) {
seenRegion = true;
for (char c : subtag) parsed.region += Detail::Upper(c);
} else {
tail = true;
}
}
++index;
if (end == std::string_view::npos) break;
position = end + 1;
}
tag = std::move(parsed);
return true;
}
inline std::string NormalizePosixLocale(std::string_view text) {
std::string_view modifier;
if (const std::size_t at = text.find('@'); at != std::string_view::npos) {
modifier = text.substr(at + 1);
text = text.substr(0, at);
}
text = text.substr(0, text.find('.'));
std::string name;
for (char c : text) name += c == '_' ? '-' : c;
if (name.empty() || Detail::EqualsIgnoringCase(name, "C") || Detail::EqualsIgnoringCase(name, "POSIX")) return {};
std::string_view script;
if (Detail::EqualsIgnoringCase(modifier, "latin")) script = "Latn";
if (Detail::EqualsIgnoringCase(modifier, "cyrillic")) script = "Cyrl";
if (!script.empty()) {
const std::size_t dash = name.find('-');
const std::string_view second = dash == std::string::npos ? std::string_view{} : std::string_view(name).substr(dash + 1, 4);
const bool hasScript = second.size() == 4 && Detail::AllOf(second, Detail::IsAlpha) && (dash + 5 == name.size() || name[dash + 5] == '-');
if (!hasScript) name.insert(dash == std::string::npos ? name.size() : dash, std::string("-") + std::string(script));
}
return name;
}
inline bool ParseLocale(std::string_view text, Tag& tag) {
const std::string normalised = NormalizePosixLocale(text);
return !normalised.empty() && ParseTag(normalised, tag);
}
}
#endif