Files
AnyPS5/core/libs/prx/libSceSystemService/LanguageLocale.hpp
T
MarcZX10 b9f716124d feat(libSceSystemService): map Chinese locales to Traditional or Simplified
A script decides first (zh-Hant is Traditional, zh-Hans Simplified, also
against a contradicting region such as zh-Hans-TW), then the region (TW, HK and
MO are Traditional), and a bare zh is Simplified. Mandarin (cmn) follows the
same rules and Cantonese (yue) is Traditional unless it names Hans.
2026-10-29 08:51:16 +00:00

240 lines
7.9 KiB
C++

#ifndef CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_LANGUAGELOCALE_HPP
#define CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_LANGUAGELOCALE_HPP
#include <cstddef>
#include <span>
#include <string>
#include <string_view>
#include <utility>
namespace SystemServiceLocale {
struct Tag {
std::string language;
std::string script;
std::string region;
};
enum ConsoleLanguage : int {
Japanese = 0,
EnglishUs = 1,
French = 2,
Spanish = 3,
German = 4,
Italian = 5,
Dutch = 6,
PortuguesePortugal = 7,
Russian = 8,
Korean = 9,
ChineseTraditional = 10,
ChineseSimplified = 11,
Finnish = 12,
Swedish = 13,
Danish = 14,
Norwegian = 15,
Polish = 16,
PortugueseBrazil = 17,
EnglishUk = 18,
Turkish = 19,
SpanishLatinAmerica = 20,
Arabic = 21,
FrenchCanada = 22,
Czech = 23,
Hungarian = 24,
Greek = 25,
Romanian = 26,
Thai = 27,
Vietnamese = 28,
Indonesian = 29,
Ukrainian = 30,
};
constexpr int NoConsoleLanguage = -1;
struct Row {
std::string_view language;
std::string_view script;
std::string_view region;
int id;
};
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 constexpr Row LatinEuropeanRows[] = {
{"en", "", "", EnglishUs},
{"en", "", "GB", EnglishUk},
{"en", "", "IE", EnglishUk},
{"en", "", "AU", EnglishUk},
{"en", "", "NZ", EnglishUk},
{"fr", "", "", French},
{"fr", "", "CA", FrenchCanada},
{"de", "", "", German},
{"it", "", "", Italian},
{"nl", "", "", Dutch},
{"ro", "", "", Romanian},
{"mo", "", "", Romanian},
};
inline constexpr Row IberianRows[] = {
{"es", "", "", Spanish},
{"es", "", "419", SpanishLatinAmerica},
{"es", "", "MX", SpanishLatinAmerica},
{"es", "", "AR", SpanishLatinAmerica},
{"es", "", "BO", SpanishLatinAmerica},
{"es", "", "CL", SpanishLatinAmerica},
{"es", "", "CO", SpanishLatinAmerica},
{"es", "", "CR", SpanishLatinAmerica},
{"es", "", "CU", SpanishLatinAmerica},
{"es", "", "DO", SpanishLatinAmerica},
{"es", "", "EC", SpanishLatinAmerica},
{"es", "", "GT", SpanishLatinAmerica},
{"es", "", "HN", SpanishLatinAmerica},
{"es", "", "NI", SpanishLatinAmerica},
{"es", "", "PA", SpanishLatinAmerica},
{"es", "", "PE", SpanishLatinAmerica},
{"es", "", "PR", SpanishLatinAmerica},
{"es", "", "PY", SpanishLatinAmerica},
{"es", "", "SV", SpanishLatinAmerica},
{"es", "", "US", SpanishLatinAmerica},
{"es", "", "UY", SpanishLatinAmerica},
{"es", "", "VE", SpanishLatinAmerica},
{"pt", "", "", PortugueseBrazil},
{"pt", "", "PT", PortuguesePortugal},
{"pt", "", "AO", PortuguesePortugal},
{"pt", "", "MZ", PortuguesePortugal},
{"pt", "", "CV", PortuguesePortugal},
{"pt", "", "GW", PortuguesePortugal},
{"pt", "", "ST", PortuguesePortugal},
{"pt", "", "TL", PortuguesePortugal},
{"pt", "", "MO", PortuguesePortugal},
};
inline constexpr Row ChineseRows[] = {
{"zh", "", "", ChineseSimplified},
{"zh", "Hans", "", ChineseSimplified},
{"zh", "Hant", "", ChineseTraditional},
{"zh", "", "TW", ChineseTraditional},
{"zh", "", "HK", ChineseTraditional},
{"zh", "", "MO", ChineseTraditional},
{"cmn", "", "", ChineseSimplified},
{"cmn", "Hant", "", ChineseTraditional},
{"cmn", "", "TW", ChineseTraditional},
{"yue", "", "", ChineseTraditional},
{"yue", "Hans", "", ChineseSimplified},
};
inline constexpr std::span<const Row> RowGroups[] = {
LatinEuropeanRows,
IberianRows,
ChineseRows,
};
}
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);
}
inline int ConsoleLanguageForTag(const Tag& tag) {
int best = NoConsoleLanguage;
int bestScore = -1;
for (const std::span<const Row> group : Detail::RowGroups) {
for (const Row& row : group) {
if (row.language != tag.language) continue;
if (!row.script.empty() && row.script != tag.script) continue;
if (!row.region.empty() && row.region != tag.region) continue;
const int score = (row.script.empty() ? 0 : 2) + (row.region.empty() ? 0 : 1);
if (score > bestScore) {
best = row.id;
bestScore = score;
}
}
}
return best;
}
}
#endif