Files
AnyPS5/core/libs/prx/libSceSystemService/LanguageLocale.hpp
T
MarcZX10 e439a2afe1 feat(libSceSystemService): map Slavic and Nordic locales
Russian, Ukrainian, Polish, Czech, Finnish, Swedish and Danish map to their
console language. Norwegian is the console's one Norwegian language, so no,
nb (Bokmal) and nn (Nynorsk) all map to it. Lithuanian, Latvian, Estonian and
the other Slavic languages the console has no entry for stay unrecognised.
2026-10-29 08:51:17 +00:00

260 lines
8.4 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 Row JapaneseKoreanRows[] = {
{"ja", "", "", Japanese},
{"ko", "", "", Korean},
};
inline constexpr Row SlavicNordicRows[] = {
{"ru", "", "", Russian},
{"uk", "", "", Ukrainian},
{"pl", "", "", Polish},
{"cs", "", "", Czech},
{"fi", "", "", Finnish},
{"sv", "", "", Swedish},
{"da", "", "", Danish},
{"no", "", "", Norwegian},
{"nb", "", "", Norwegian},
{"nn", "", "", Norwegian},
};
inline constexpr std::span<const Row> RowGroups[] = {
LatinEuropeanRows,
IberianRows,
ChineseRows,
JapaneseKoreanRows,
SlavicNordicRows,
};
}
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