915 lines
24 KiB
Python
915 lines
24 KiB
Python
# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person
|
|
# obtaining a copy of this software and associated documentation files
|
|
# (the "Software"), to deal in the Software without restriction,
|
|
# including without limitation the rights to use, copy, modify, merge,
|
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
# and to permit persons to whom the Software is furnished to do so,
|
|
# subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
from __future__ import print_function
|
|
|
|
import renpy
|
|
|
|
import hashlib
|
|
import re
|
|
import collections
|
|
import os
|
|
import time
|
|
import io
|
|
import codecs
|
|
|
|
################################################################################
|
|
# Script
|
|
################################################################################
|
|
|
|
|
|
class ScriptTranslator(object):
|
|
|
|
def __init__(self):
|
|
|
|
# All languages we know about.
|
|
self.languages = set()
|
|
|
|
# A map from the translate identifier to the translate object used when the
|
|
# language is None.
|
|
self.default_translates = { }
|
|
|
|
# A map from (identifier, language) to the translate object used for that
|
|
# language.
|
|
self.language_translates = { }
|
|
|
|
# A list of (identifier, language) tuples that we need to chain together.
|
|
self.chain_worklist = [ ]
|
|
|
|
# A map from filename to a list of (label, translate) pairs found in
|
|
# that file.
|
|
self.file_translates = collections.defaultdict(list)
|
|
|
|
# A map from language to the StringTranslator for that language.
|
|
self.strings = collections.defaultdict(StringTranslator)
|
|
|
|
# A map from language to a list of TranslateBlock objects for
|
|
# that language.
|
|
self.block = collections.defaultdict(list)
|
|
|
|
# A map from language to a list of TranslateEarlyBlock objects for
|
|
# that language.
|
|
self.early_block = collections.defaultdict(list)
|
|
|
|
# A map from language to a list of TranslatePython objects for
|
|
# that language.
|
|
self.python = collections.defaultdict(list)
|
|
|
|
# A map from filename to a list of additional strings we've found
|
|
# in that file.
|
|
self.additional_strings = collections.defaultdict(list)
|
|
|
|
def count_translates(self):
|
|
"""
|
|
Return the number of dialogue blocks in the game.
|
|
"""
|
|
|
|
return len(self.default_translates)
|
|
|
|
def take_translates(self, nodes):
|
|
"""
|
|
Takes the translates out of the flattened list of statements, and stores
|
|
them into the dicts above.
|
|
"""
|
|
|
|
label = None
|
|
|
|
if not nodes:
|
|
return
|
|
|
|
TranslatePython = renpy.ast.TranslatePython
|
|
TranslateBlock = renpy.ast.TranslateBlock
|
|
TranslateEarlyBlock = renpy.ast.TranslateEarlyBlock
|
|
Menu = renpy.ast.Menu
|
|
UserStatement = renpy.ast.UserStatement
|
|
Translate = renpy.ast.Translate
|
|
|
|
filename = renpy.exports.unelide_filename(nodes[0].filename)
|
|
filename = os.path.normpath(os.path.abspath(filename))
|
|
|
|
for n in nodes:
|
|
|
|
if not n.translation_relevant:
|
|
continue
|
|
|
|
if n.name.__class__ is not tuple:
|
|
if isinstance(n.name, basestring):
|
|
label = n.name
|
|
|
|
type_n = n.__class__
|
|
|
|
if type_n is TranslatePython:
|
|
if n.language is not None:
|
|
self.languages.add(n.language)
|
|
self.python[n.language].append(n)
|
|
|
|
elif type_n is TranslateEarlyBlock:
|
|
if n.language is not None:
|
|
self.languages.add(n.language)
|
|
self.early_block[n.language].append(n)
|
|
|
|
elif type_n is TranslateBlock:
|
|
if n.language is not None:
|
|
self.languages.add(n.language)
|
|
self.block[n.language].append(n)
|
|
|
|
elif type_n is Menu:
|
|
|
|
for i in n.items:
|
|
s = i[0]
|
|
|
|
if s is None:
|
|
continue
|
|
|
|
self.additional_strings[filename].append((n.linenumber, s))
|
|
|
|
elif type_n is UserStatement:
|
|
|
|
strings = n.call("translation_strings")
|
|
|
|
if strings is None:
|
|
continue
|
|
|
|
for s in strings:
|
|
self.additional_strings[filename].append((n.linenumber, s))
|
|
|
|
elif type_n is Translate:
|
|
|
|
if n.language is None:
|
|
self.default_translates[n.identifier] = n
|
|
self.file_translates[filename].append((label, n))
|
|
else:
|
|
self.languages.add(n.language)
|
|
self.language_translates[n.identifier, n.language] = n
|
|
self.chain_worklist.append((n.identifier, n.language))
|
|
|
|
def chain_translates(self):
|
|
"""
|
|
Chains nodes in non-default translates together.
|
|
"""
|
|
|
|
unchained = [ ]
|
|
|
|
for identifier, language in self.chain_worklist:
|
|
|
|
if identifier not in self.default_translates:
|
|
unchained.append((identifier, language))
|
|
continue
|
|
|
|
translate = self.language_translates[identifier, language]
|
|
next_node = self.default_translates[identifier].after
|
|
|
|
renpy.ast.chain_block(translate.block, next_node)
|
|
|
|
self.chain_worklist = unchained
|
|
|
|
def lookup_translate(self, identifier, alternate=None):
|
|
|
|
identifier = identifier.replace('.', '_')
|
|
language = renpy.game.preferences.language
|
|
|
|
if language is not None:
|
|
tl = self.language_translates.get((identifier, language), None)
|
|
|
|
if (tl is None) and alternate:
|
|
tl = self.language_translates.get((identifier, language), None)
|
|
|
|
else:
|
|
tl = None
|
|
|
|
if tl is None:
|
|
tl = self.default_translates[identifier]
|
|
|
|
return tl.block[0]
|
|
|
|
|
|
def encode_say_string(s):
|
|
"""
|
|
Encodes a string in the format used by Ren'Py say statements.
|
|
"""
|
|
|
|
s = s.replace("\\", "\\\\")
|
|
s = s.replace("\n", "\\n")
|
|
s = s.replace("\"", "\\\"")
|
|
s = re.sub(r'(?<= ) ', '\\ ', s)
|
|
|
|
return "\"" + s + "\""
|
|
|
|
|
|
class Restructurer(object):
|
|
|
|
def __init__(self, children):
|
|
self.label = None
|
|
self.alternate = None
|
|
|
|
self.identifiers = set()
|
|
self.callback(children)
|
|
|
|
def id_exists(self, identifier):
|
|
if identifier in self.identifiers:
|
|
return True
|
|
|
|
if identifier in renpy.game.script.translator.default_translates: # @UndefinedVariable
|
|
return True
|
|
|
|
return False
|
|
|
|
def unique_identifier(self, label, digest):
|
|
|
|
if label is None:
|
|
base = digest
|
|
else:
|
|
base = label.replace(".", "_") + "_" + digest
|
|
|
|
i = 0
|
|
suffix = ""
|
|
|
|
while True:
|
|
|
|
identifier = base + suffix
|
|
|
|
if not self.id_exists(identifier):
|
|
break
|
|
|
|
i += 1
|
|
suffix = "_{0}".format(i)
|
|
|
|
return identifier
|
|
|
|
def create_translate(self, block):
|
|
"""
|
|
Creates an ast.Translate that wraps `block`. The block may only contain
|
|
translatable statements.
|
|
"""
|
|
|
|
md5 = hashlib.md5()
|
|
|
|
for i in block:
|
|
code = i.get_code()
|
|
md5.update(code.encode("utf-8") + "\r\n")
|
|
|
|
digest = md5.hexdigest()[:8]
|
|
|
|
identifier = self.unique_identifier(self.label, digest)
|
|
self.identifiers.add(identifier)
|
|
|
|
if self.alternate is not None:
|
|
alternate = self.unique_identifier(self.alternate, digest)
|
|
self.identifiers.add(alternate)
|
|
else:
|
|
alternate = None
|
|
|
|
loc = (block[0].filename, block[0].linenumber)
|
|
|
|
tl = renpy.ast.Translate(loc, identifier, None, block, alternate=alternate)
|
|
tl.name = block[0].name + ("translate",)
|
|
|
|
ed = renpy.ast.EndTranslate(loc)
|
|
ed.name = block[0].name + ("end_translate",)
|
|
|
|
return [ tl, ed ]
|
|
|
|
def callback(self, children):
|
|
"""
|
|
This should be called with a list of statements. It restructures the statements
|
|
in the list so that translatable statements are contained within translation blocks.
|
|
"""
|
|
|
|
new_children = [ ]
|
|
group = [ ]
|
|
|
|
for i in children:
|
|
|
|
if isinstance(i, renpy.ast.Label):
|
|
if not i.hide:
|
|
|
|
if i.name.startswith("_"):
|
|
self.alternate = i.name
|
|
else:
|
|
self.label = i.name
|
|
self.alternate = None
|
|
|
|
if not isinstance(i, renpy.ast.Translate):
|
|
i.restructure(self.callback)
|
|
|
|
if isinstance(i, renpy.ast.Say):
|
|
group.append(i)
|
|
tl = self.create_translate(group)
|
|
new_children.extend(tl)
|
|
group = [ ]
|
|
|
|
elif i.translatable:
|
|
group.append(i)
|
|
|
|
else:
|
|
if group:
|
|
tl = self.create_translate(group)
|
|
new_children.extend(tl)
|
|
group = [ ]
|
|
|
|
new_children.append(i)
|
|
|
|
if group:
|
|
nodes = self.create_translate(group)
|
|
new_children.extend(nodes)
|
|
group = [ ]
|
|
|
|
children[:] = new_children
|
|
|
|
|
|
def restructure(children):
|
|
Restructurer(children)
|
|
|
|
|
|
################################################################################
|
|
# String Translation
|
|
################################################################################
|
|
|
|
update_translations = ("RENPY_UPDATE_STRINGS" in os.environ)
|
|
|
|
|
|
def quote_unicode(s):
|
|
s = s.replace("\\", "\\\\")
|
|
s = s.replace("\"", "\\\"")
|
|
s = s.replace("\a", "\\a")
|
|
s = s.replace("\b", "\\b")
|
|
s = s.replace("\f", "\\f")
|
|
s = s.replace("\n", "\\n")
|
|
s = s.replace("\r", "\\r")
|
|
s = s.replace("\t", "\\t")
|
|
s = s.replace("\v", "\\v")
|
|
|
|
return s
|
|
|
|
|
|
class StringTranslator(object):
|
|
"""
|
|
This object stores the translations for a single language. It can also
|
|
buffer unknown translations, and write them to a file at game's end, if
|
|
we want that to happen.
|
|
"""
|
|
|
|
def __init__(self):
|
|
|
|
# A map from translation to translated string.
|
|
self.translations = { }
|
|
|
|
# A map from translation to the location of the translated string.
|
|
self.translation_loc = { }
|
|
|
|
# A list of unknown translations.
|
|
self.unknown = [ ]
|
|
|
|
def add(self, old, new, newloc):
|
|
if old in self.translations:
|
|
|
|
if old in self.translation_loc:
|
|
print(newloc, self.translation_loc[old])
|
|
fn, line = self.translation_loc[old]
|
|
raise Exception("A translation for \"{}\" already exists at {}:{}.".format(
|
|
quote_unicode(old), fn, line))
|
|
else:
|
|
raise Exception("A translation for \"{}\" already exists.".format(
|
|
quote_unicode(old)))
|
|
|
|
self.translations[old] = new
|
|
|
|
if newloc is not None:
|
|
self.translation_loc[old] = newloc
|
|
|
|
def translate(self, old):
|
|
|
|
new = self.translations.get(old, None)
|
|
|
|
if new is not None:
|
|
return new
|
|
|
|
if update_translations:
|
|
self.translations[old] = old
|
|
self.unknown.append(old)
|
|
|
|
# Remove {#...} tags.
|
|
if new is None:
|
|
notags = re.sub(r"\{\#.*?\}", "", old)
|
|
new = self.translations.get(notags, None)
|
|
|
|
if new is not None:
|
|
return new
|
|
|
|
return old
|
|
|
|
def write_updated_strings(self, language):
|
|
|
|
if not self.unknown:
|
|
return
|
|
|
|
if language is None:
|
|
fn = os.path.join(renpy.config.gamedir, "strings.rpy")
|
|
else:
|
|
fn = os.path.join(renpy.config.gamedir, renpy.config.tl_directory, language, "strings.rpy")
|
|
|
|
f = renpy.translation.generation.open_tl_file(fn)
|
|
|
|
f.write(u"translate {} strings:\n".format(language))
|
|
f.write(u"\n")
|
|
|
|
for i in self.unknown:
|
|
|
|
i = quote_unicode(i)
|
|
|
|
f.write(u" old \"{}\"\n".format(i))
|
|
f.write(u" new \"{}\"\n".format(i))
|
|
f.write(u"\n")
|
|
|
|
f.close()
|
|
|
|
|
|
def add_string_translation(language, old, new, newloc):
|
|
|
|
tl = renpy.game.script.translator
|
|
stl = tl.strings[language]
|
|
tl.languages.add(language)
|
|
stl.add(old, new, newloc)
|
|
|
|
|
|
Default = renpy.object.Sentinel("default")
|
|
|
|
|
|
def translate_string(s, language=Default):
|
|
"""
|
|
:doc: translate_string
|
|
:name: renpy.translate_string
|
|
|
|
Translates interface string `s` to `language`. If `language` is Default,
|
|
uses the language set in the preferences. This does not mark `s` to be
|
|
translated.
|
|
"""
|
|
|
|
if language is Default:
|
|
language = renpy.game.preferences.language
|
|
|
|
stl = renpy.game.script.translator.strings[language] # @UndefinedVariable
|
|
return stl.translate(s)
|
|
|
|
|
|
def write_updated_strings():
|
|
stl = renpy.game.script.translator.strings[renpy.game.preferences.language] # @UndefinedVariable
|
|
stl.write_updated_strings(renpy.game.preferences.language)
|
|
|
|
|
|
################################################################################
|
|
# RPT Support
|
|
#
|
|
# RPT was the translation format used before 6.15.
|
|
################################################################################
|
|
|
|
def load_rpt(fn):
|
|
"""
|
|
Loads the .rpt file `fn`.
|
|
"""
|
|
|
|
def unquote(s):
|
|
s = s.replace("\\n", "\n")
|
|
s = s.replace("\\\\", "\\")
|
|
return s
|
|
|
|
language = os.path.basename(fn).replace(".rpt", "")
|
|
|
|
f = renpy.loader.load(fn)
|
|
|
|
old = None
|
|
|
|
for l in f:
|
|
l = l.decode("utf-8")
|
|
l = l.rstrip()
|
|
|
|
if not l:
|
|
continue
|
|
|
|
if l[0] == '#':
|
|
continue
|
|
|
|
s = unquote(l[2:])
|
|
|
|
if l[0] == '<':
|
|
if old:
|
|
raise Exception("{0} string {1!r} does not have a translation.".format(language, old))
|
|
|
|
old = s
|
|
|
|
if l[0] == ">":
|
|
if old is None:
|
|
raise Exception("{0} translation {1!r} doesn't belong to a string.".format(language, s))
|
|
|
|
add_string_translation(language, old, s, None)
|
|
old = None
|
|
|
|
f.close()
|
|
|
|
if old is not None:
|
|
raise Exception("{0} string {1!r} does not have a translation.".format(language, old))
|
|
|
|
|
|
def load_all_rpts():
|
|
"""
|
|
Loads all .rpt files.
|
|
"""
|
|
|
|
for fn in renpy.exports.list_files():
|
|
if fn.endswith(".rpt"):
|
|
load_rpt(fn)
|
|
|
|
################################################################################
|
|
# Changing language
|
|
################################################################################
|
|
|
|
|
|
style_backup = None
|
|
|
|
|
|
def init_translation():
|
|
"""
|
|
Called before the game starts.
|
|
"""
|
|
|
|
global style_backup
|
|
style_backup = renpy.style.backup() # @UndefinedVariable
|
|
|
|
load_all_rpts()
|
|
|
|
renpy.store._init_language() # @UndefinedVariable
|
|
|
|
|
|
old_language = "language never set"
|
|
|
|
# A list of styles that have beend deferred to right before translate
|
|
# styles are run.
|
|
deferred_styles = [ ]
|
|
|
|
|
|
def old_change_language(tl, language):
|
|
|
|
for i in deferred_styles:
|
|
i.apply()
|
|
|
|
def run_blocks():
|
|
for i in tl.early_block[language]:
|
|
renpy.game.context().run(i.block[0])
|
|
|
|
for i in tl.block[language]:
|
|
renpy.game.context().run(i.block[0])
|
|
|
|
renpy.game.invoke_in_new_context(run_blocks)
|
|
|
|
for i in tl.python[language]:
|
|
renpy.python.py_exec_bytecode(i.code.bytecode)
|
|
|
|
for i in renpy.config.language_callbacks[language]:
|
|
i()
|
|
|
|
|
|
def new_change_language(tl, language):
|
|
|
|
for i in tl.python[language]:
|
|
renpy.python.py_exec_bytecode(i.code.bytecode)
|
|
|
|
def run_blocks():
|
|
for i in tl.early_block[language]:
|
|
renpy.game.context().run(i.block[0])
|
|
|
|
renpy.game.invoke_in_new_context(run_blocks)
|
|
|
|
for i in renpy.config.language_callbacks[language]:
|
|
i()
|
|
|
|
for i in deferred_styles:
|
|
i.apply()
|
|
|
|
def run_blocks():
|
|
for i in tl.block[language]:
|
|
renpy.game.context().run(i.block[0])
|
|
|
|
renpy.game.invoke_in_new_context(run_blocks)
|
|
|
|
renpy.config.init_system_styles()
|
|
|
|
|
|
def change_language(language, force=False):
|
|
"""
|
|
:doc: translation_functions
|
|
|
|
Changes the current language to `language`, which can be a string or
|
|
None to use the default language.
|
|
"""
|
|
|
|
global old_language
|
|
|
|
renpy.game.preferences.language = language
|
|
if old_language == language and not force:
|
|
return
|
|
|
|
tl = renpy.game.script.translator
|
|
|
|
renpy.style.restore(style_backup) # @UndefinedVariable
|
|
renpy.style.rebuild() # @UndefinedVariable
|
|
|
|
for i in renpy.config.translate_clean_stores:
|
|
renpy.python.clean_store(i)
|
|
|
|
if renpy.config.new_translate_order:
|
|
new_change_language(tl, language)
|
|
else:
|
|
old_change_language(tl, language)
|
|
|
|
renpy.store._history_list = renpy.store.list()
|
|
renpy.store.nvl_list = renpy.store.list()
|
|
|
|
for i in renpy.config.change_language_callbacks:
|
|
i()
|
|
|
|
# Reset various parts of the system. Most notably, this clears the image
|
|
# cache, letting us load translated images.
|
|
renpy.exports.free_memory()
|
|
|
|
# Rebuild the styles.
|
|
renpy.style.rebuild() # @UndefinedVariable
|
|
|
|
for i in renpy.config.translate_clean_stores:
|
|
renpy.python.reset_store_changes(i)
|
|
|
|
# Restart the interaction.
|
|
renpy.exports.restart_interaction()
|
|
|
|
if language != old_language:
|
|
renpy.exports.block_rollback()
|
|
|
|
old_language = language
|
|
|
|
def check_language():
|
|
"""
|
|
Checks to see if the language has changed. If it has, jump to the start
|
|
of the current translation block.
|
|
"""
|
|
|
|
ctx = renpy.game.contexts[-1]
|
|
preferences = renpy.game.preferences
|
|
|
|
# Deal with a changed language.
|
|
if ctx.translate_language != preferences.language:
|
|
ctx.translate_language = preferences.language
|
|
|
|
tid = ctx.translate_identifier
|
|
|
|
if tid is not None:
|
|
node = renpy.game.script.translator.lookup_translate(tid) # @UndefinedVariable
|
|
|
|
if node is not None:
|
|
raise renpy.game.JumpException(node.name)
|
|
|
|
|
|
def known_languages():
|
|
"""
|
|
:doc: translation_functions
|
|
|
|
Returns the set of known languages. This does not include the default
|
|
language, None.
|
|
"""
|
|
|
|
return { i for i in renpy.game.script.translator.languages if i is not None } # @UndefinedVariable
|
|
|
|
################################################################################
|
|
# Detect language
|
|
################################################################################
|
|
|
|
|
|
locales = {
|
|
"ab": "abkhazian",
|
|
"aa": "afar",
|
|
"af": "afrikaans",
|
|
"ak": "akan",
|
|
"sq": "albanian",
|
|
"am": "amharic",
|
|
"ar": "arabic",
|
|
"an": "aragonese",
|
|
"hy": "armenian",
|
|
"as": "assamese",
|
|
"av": "avaric",
|
|
"ae": "avestan",
|
|
"ay": "aymara",
|
|
"az": "azerbaijani",
|
|
"bm": "bambara",
|
|
"ba": "bashkir",
|
|
"eu": "basque",
|
|
"be": "belarusian",
|
|
"bn": "bengali",
|
|
"bh": "bihari",
|
|
"bi": "bislama",
|
|
"bs": "bosnian",
|
|
"br": "breton",
|
|
"bg": "bulgarian",
|
|
"my": "burmese",
|
|
"ca": "catalan",
|
|
"ch": "chamorro",
|
|
"ce": "chechen",
|
|
"ny": "chewa",
|
|
"cv": "chuvash",
|
|
"kw": "cornish",
|
|
"co": "corsican",
|
|
"cr": "cree",
|
|
"hr": "croatian",
|
|
"cs": "czech",
|
|
"da": "danish",
|
|
"dv": "maldivian",
|
|
"nl": "dutch",
|
|
"dz": "dzongkha",
|
|
"en": "english",
|
|
"et": "estonian",
|
|
"ee": "ewe",
|
|
"fo": "faroese",
|
|
"fj": "fijian",
|
|
"fi": "finnish",
|
|
"fr": "french",
|
|
"ff": "fulah",
|
|
"gl": "galician",
|
|
"ka": "georgian",
|
|
"de": "german",
|
|
"el": "greek",
|
|
"gn": "guaran",
|
|
"gu": "gujarati",
|
|
"ht": "haitian",
|
|
"ha": "hausa",
|
|
"he": "hebrew",
|
|
"hz": "herero",
|
|
"hi": "hindi",
|
|
"ho": "hiri_motu",
|
|
"hu": "hungarian",
|
|
"id": "indonesian",
|
|
"ga": "irish",
|
|
"ig": "igbo",
|
|
"ik": "inupiaq",
|
|
"is": "icelandic",
|
|
"it": "italian",
|
|
"iu": "inuktitut",
|
|
"ja": "japanese",
|
|
"jv": "javanese",
|
|
"kl": "greenlandic",
|
|
"kn": "kannada",
|
|
"kr": "kanuri",
|
|
"ks": "kashmiri",
|
|
"kk": "kazakh",
|
|
"km": "khmer",
|
|
"ki": "kikuyu",
|
|
"rw": "kinyarwanda",
|
|
"ky": "kirghiz",
|
|
"kv": "komi",
|
|
"kg": "kongo",
|
|
"ko": "korean",
|
|
"ku": "kurdish",
|
|
"kj": "kuanyama",
|
|
"la": "latin",
|
|
"lb": "luxembourgish",
|
|
"lg": "ganda",
|
|
"li": "limburgan",
|
|
"ln": "lingala",
|
|
"lo": "lao",
|
|
"lt": "lithuanian",
|
|
"lv": "latvian",
|
|
"gv": "manx",
|
|
"mk": "macedonian",
|
|
"mg": "malagasy",
|
|
"ms": "malay",
|
|
"ml": "malayalam",
|
|
"mt": "maltese",
|
|
"mi": "maori",
|
|
"mr": "marathi",
|
|
"mh": "marshallese",
|
|
"mn": "mongolian",
|
|
"na": "nauru",
|
|
"nv": "navaho",
|
|
"ne": "nepali",
|
|
"ng": "ndonga",
|
|
"no": "norwegian",
|
|
"ii": "nuosu",
|
|
"nr": "ndebele",
|
|
"oc": "occitan",
|
|
"oj": "ojibwa",
|
|
"om": "oromo",
|
|
"or": "oriya",
|
|
"os": "ossetian",
|
|
"pa": "panjabi",
|
|
"pi": "pali",
|
|
"fa": "persian",
|
|
"pl": "polish",
|
|
"ps": "pashto",
|
|
"pt": "portuguese",
|
|
"qu": "quechua",
|
|
"rm": "romansh",
|
|
"rn": "rundi",
|
|
"ro": "romanian",
|
|
"ru": "russian",
|
|
"sa": "sanskrit",
|
|
"sc": "sardinian",
|
|
"sd": "sindhi",
|
|
"se": "sami",
|
|
"sm": "samoan",
|
|
"sg": "sango",
|
|
"sr": "serbian",
|
|
"gd": "gaelic",
|
|
"sn": "shona",
|
|
"si": "sinhala",
|
|
"sk": "slovak",
|
|
"sl": "slovene",
|
|
"so": "somali",
|
|
"st": "sotho",
|
|
"es": "spanish",
|
|
"su": "sundanese",
|
|
"sw": "swahili",
|
|
"ss": "swati",
|
|
"sv": "swedish",
|
|
"ta": "tamil",
|
|
"te": "telugu",
|
|
"tg": "tajik",
|
|
"th": "thai",
|
|
"ti": "tigrinya",
|
|
"bo": "tibetan",
|
|
"tk": "turkmen",
|
|
"tl": "tagalog",
|
|
"tn": "tswana",
|
|
"to": "tongan",
|
|
"tr": "turkish",
|
|
"ts": "tsonga",
|
|
"tt": "tatar",
|
|
"tw": "twi",
|
|
"ty": "tahitian",
|
|
"ug": "uighur",
|
|
"uk": "ukrainian",
|
|
"ur": "urdu",
|
|
"uz": "uzbek",
|
|
"ve": "venda",
|
|
"vi": "vietnamese",
|
|
"wa": "walloon",
|
|
"cy": "welsh",
|
|
"wo": "wolof",
|
|
"fy": "frisian",
|
|
"xh": "xhosa",
|
|
"yi": "yiddish",
|
|
"yo": "yoruba",
|
|
"za": "zhuang",
|
|
"zu": "zulu",
|
|
"chs": "simplified_chinese",
|
|
"cht": "traditional_chinese",
|
|
"zh": "traditional_chinese",
|
|
}
|
|
|
|
|
|
def detect_user_locale():
|
|
import locale
|
|
if renpy.windows:
|
|
import ctypes
|
|
windll = ctypes.windll.kernel32
|
|
locale_name = locale.windows_locale.get(windll.GetUserDefaultUILanguage())
|
|
elif renpy.android:
|
|
from jnius import autoclass
|
|
Locale = autoclass('java.util.Locale')
|
|
locale_name = str(Locale.getDefault().getLanguage())
|
|
elif renpy.ios:
|
|
import pyobjus
|
|
NSLocale = pyobjus.autoclass("NSLocale")
|
|
languages = NSLocale.preferredLanguages()
|
|
locale_name = languages.objectAtIndex_(0).UTF8String().decode("utf-8")
|
|
locale_name.replace("-", "_")
|
|
else:
|
|
locale_name = locale.getdefaultlocale()
|
|
if locale_name is not None:
|
|
locale_name = locale_name[0]
|
|
|
|
if locale_name is None:
|
|
return None, None
|
|
|
|
normalize = locale.normalize(locale_name)
|
|
if normalize == locale_name:
|
|
language = region = locale_name
|
|
else:
|
|
locale_name = normalize
|
|
if '.' in locale_name:
|
|
locale_name, _ = locale_name.split('.', 1)
|
|
language, region = locale_name.lower().split("_")
|
|
return language, region
|