VVVVVV/desktop_version/src/Localization.cpp

429 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define LOCALIZATION_CPP
#include "Localization.h"
#include "LocalizationStorage.h"
#include "Alloc.h"
#include "Game.h"
#include "UTF8.h"
#include "UtilityClass.h"
#include "VFormat.h"
namespace loc
{
int lang_set = 0;
bool pre_title_lang_menu = false;
std::string lang = "en";
std::string lang_custom = "";
bool english_sprites = false;
std::string new_level_font = "";
LangMeta langmeta;
// language screen list
std::vector<LangMeta> languagelist;
int languagelist_curlang;
bool show_translator_menu = false;
size_t limitscheck_current_overflow;
std::vector<std::string> testable_script_ids;
int n_untranslated_roomnames = 0;
int n_unexplained_roomnames = 0;
int n_untranslated_roomnames_custom = 0;
int n_unexplained_roomnames_custom = 0;
int n_untranslated_roomnames_area[9];
int n_untranslated[COUNT_UNTRANSLATED_INDEX] = {0};
const LangMeta* get_langmeta(void)
{
if (game.currentmenuname == Menu::language && (unsigned)game.currentmenuoption < languagelist.size())
{
return &languagelist[game.currentmenuoption];
}
return &langmeta;
}
const char* gettext(const char* eng)
{
if (lang == "en")
{
return eng;
}
return map_lookup_text(map_translation, eng, eng);
}
const char* gettext_case(const char* eng, char textcase)
{
if (lang == "en")
{
return eng;
}
if (textcase == 0)
{
return gettext(eng);
}
char* eng_prefixed = add_disambiguator(textcase, eng, NULL);
if (eng_prefixed == NULL)
{
return eng;
}
const char* tra = map_lookup_text(map_translation, eng_prefixed, eng);
VVV_free(eng_prefixed);
return tra;
}
static const char* gettext_plural_english(const char* eng_plural, const char* eng_singular, int n)
{
/* Do be consistent with negative number handling for other languages... */
if (n == 1 || n == -1)
{
return eng_singular;
}
return eng_plural;
}
const char* gettext_plural(const char* eng_plural, const char* eng_singular, int n)
{
if (lang != "en")
{
unsigned char form = form_for_count(n);
char* key = add_disambiguator(form+1, eng_plural, NULL);
if (key != NULL)
{
const char* tra = map_lookup_text(map_translation_plural, key, NULL);
VVV_free(key);
if (tra != NULL)
{
return tra;
}
}
}
return gettext_plural_english(eng_plural, eng_singular, n);
}
void gettext_plural_fill(char* buf, size_t buf_len, const char* eng_plural, const char* eng_singular, const char* args_index, ...)
{
/* Choose the right plural string based on a number, and then vformat that string.
* The first vararg determines the specific plural form. */
va_list args;
va_start(args, args_index);
int count = va_arg(args, int);
va_end(args);
const char* tra = gettext_plural(eng_plural, eng_singular, count);
va_start(args, args_index);
vformat_buf_valist(buf, buf_len, tra, args_index, args);
va_end(args);
}
std::string getnumber(int n, const char* number_class)
{
if (n < 0 || n > 100)
{
return help.String(n);
}
// FIXME: implement a more flexible system later, where translators define the classes
std::string (*number_ptr)[101];
if (SDL_strcmp(number_class, "wordy2") == 0)
{
number_ptr = &number2;
}
else
{
number_ptr = &number;
}
if ((*number_ptr)[n].empty())
{
return help.String(n);
}
return (*number_ptr)[n];
}
static bool is_script_custom(const char* script_id)
{
return SDL_strncmp(script_id, "custom_", 7) == 0;
}
const TextboxFormat* gettext_cutscene(const std::string& script_id, const std::string& eng, char textcase)
{
hashmap* map;
const char* map_script_key;
if (is_script_custom(script_id.c_str()))
{
map = map_translation_cutscene_custom;
map_script_key = &script_id.c_str()[7];
}
else
{
if (lang == "en")
{
return NULL;
}
map = map_translation_cutscene;
map_script_key = script_id.c_str();
}
uintptr_t ptr_cutscene_map;
bool found = hashmap_get(map, map_script_key, SDL_strlen(map_script_key), &ptr_cutscene_map);
hashmap* cutscene_map = (hashmap*) ptr_cutscene_map;
if (!found || cutscene_map == NULL)
{
return NULL;
}
size_t alloc_len;
char* key = add_disambiguator(textcase, eng.c_str(), &alloc_len);
if (key == NULL)
{
return NULL;
}
uintptr_t ptr_format;
found = hashmap_get(cutscene_map, key, alloc_len-1, &ptr_format);
const TextboxFormat* format = (TextboxFormat*) ptr_format;
VVV_free(key);
if (!found)
{
return NULL;
}
return format;
}
const char* get_roomname_explanation(bool custom_level, int roomx, int roomy)
{
/* Never returns NULL. */
if (!fix_room_coords(custom_level, &roomx, &roomy))
{
return "";
}
const char* explanation;
if (custom_level)
{
explanation = explanation_roomnames_custom[roomy][roomx];
}
else
{
explanation = explanation_roomnames[roomy][roomx];
}
if (explanation == NULL)
{
return "";
}
return explanation;
}
const char* get_roomname_translation(bool custom_level, int roomx, int roomy)
{
/* Only looks for the translation, doesn't return English fallback.
* Never returns NULL.
* Also used for room name translation mode. */
if (!fix_room_coords(custom_level, &roomx, &roomy))
{
return "";
}
const char* tra;
if (custom_level)
{
tra = translation_roomnames_custom[roomy][roomx];
}
else
{
tra = translation_roomnames[roomy][roomx];
}
if (tra == NULL)
{
return "";
}
return tra;
}
const char* gettext_roomname(bool custom_level, int roomx, int roomy, const char* eng, bool special)
{
if (!custom_level && lang == "en")
{
return eng;
}
if (special)
{
return gettext_roomname_special(eng);
}
const char* tra = get_roomname_translation(custom_level, roomx, roomy);
if (tra[0] == '\0')
{
return eng;
}
return tra;
}
const char* gettext_roomname_special(const char* eng)
{
if (lang == "en")
{
return eng;
}
return map_lookup_text(map_translation_roomnames_special, eng, eng);
}
bool is_cutscene_translated(const std::string& script_id)
{
hashmap* map;
const char* map_script_key;
if (is_script_custom(script_id.c_str()))
{
map = map_translation_cutscene_custom;
map_script_key = &script_id.c_str()[7];
}
else
{
if (lang == "en")
{
return false;
}
map = map_translation_cutscene;
map_script_key = script_id.c_str();
}
uintptr_t ptr_unused;
return hashmap_get(map, map_script_key, SDL_strlen(map_script_key), &ptr_unused);
}
uint32_t toupper_ch(uint32_t ch)
{
// Convert a single Unicode codepoint to its uppercase variant
// Supports important Latin (1 and A), Cyrillic and Greek
// Turkish i?
if (get_langmeta()->toupper_i_dot && ch == 'i') return 0x130;
// a-z?
if ('a' <= ch && ch <= 'z') return ch - 0x20;
// Latin-1 Supplement? But not the division sign
if (0xE0 <= ch && ch <= 0xFE && ch != 0xF7) return ch - 0x20;
// ß? Yes, we do have this! And otherwise we could only replace it with SS later on.
if (ch == 0xDF) return 0x1E9E;
// ÿ?
if (ch == 0xFF) return 0x178;
// Let's get some exceptions for Latin Extended-A out of the way, starting with ı
if (ch == 0x131) return 'I';
// This range between two obscure exceptions...
if (0x139 <= ch && ch <= 0x148 && ch % 2 == 0) return ch - 1;
// The rest of Latin Extended-A?
if (0x100 <= ch && ch <= 0x177 && ch % 2 == 1) return ch - 1;
// Okay, Ÿ also pushed some aside...
if (0x179 <= ch && ch <= 0x17E && ch % 2 == 0) return ch - 1;
// Can't hurt to support Romanian properly...
if (ch == 0x219 || ch == 0x21B) return ch - 1;
// Cyrillic а-я?
if (0x430 <= ch && ch <= 0x44F) return ch - 0x20;
// There's probably a good reason Cyrillic upper and lower accents are wrapped around the alphabet...
if (0x450 <= ch && ch <= 0x45F) return ch - 0x50;
// Apparently a Ukrainian letter is all the way over there, why not.
if (ch == 0x491) return ch - 1;
// Time for Greek, thankfully we're not making a lowercasing function with that double sigma!
if (ch == 0x3C2) return 0x3A3;
// The entire Greek alphabet then, along with two accented letters
if (0x3B1 <= ch && ch <= 0x3CB) return ch - 0x20;
// Unfortunately Greek accented letters are all over the place.
if (ch == 0x3AC) return 0x386;
if (0x3AD <= ch && ch <= 0x3AF) return ch - 0x25;
if (ch == 0x3CC) return 0x38C;
if (ch == 0x3CD || ch == 0x3CE) return ch - 0x3F;
// Nothing matched! Just leave it as is
return ch;
}
std::string toupper(const std::string& lower)
{
// Convert a UTF-8 string to uppercase
if (!get_langmeta()->toupper)
{
return lower;
}
std::string upper;
/* Capacity is not final, but some uppercase is more bytes than the
* lowercase equivalent, so some extra breathing room couldn't hurt... */
upper.reserve(lower.length() + 6);
const char* lower_c = lower.c_str();
uint32_t ch;
bool ignorenext = false;
while ((ch = UTF8_next(&lower_c)))
{
if (get_langmeta()->toupper_lower_escape_char && ch == '~')
{
ignorenext = true;
continue;
}
if (!ignorenext)
{
ch = toupper_ch(ch);
}
upper.append(UTF8_encode(ch).bytes);
ignorenext = false;
}
return upper;
}
std::string remove_toupper_escape_chars(const std::string& _s)
{
// No-op, except if langmeta.toupper_lower_escape_char, to remove the ~ escape character
if (!get_langmeta()->toupper_lower_escape_char)
{
return _s;
}
std::string s = std::string(_s);
for (signed int i = s.size()-1; i >= 0; i--)
{
if (s[i] == '~')
{
s.erase(i, 1);
}
}
return s;
}
} /* namespace loc */