Untitled Diff
2 removals
327 lines
15 additions
340 lines
# ../translations/strings.py
# ../translations/strings.py
"""Provides translation functionality."""
"""Provides translation functionality."""
# =============================================================================
# =============================================================================
# >> IMPORTS
# >> IMPORTS
# =============================================================================
# =============================================================================
# Python Imports
# Python Imports
# Binascii
# Binascii
from binascii import unhexlify
from binascii import unhexlify
# Codecs
# Codecs
from codecs import unicode_escape_decode
from codecs import unicode_escape_decode
# Re
# Re
from re import compile as re_compile
from re import compile as re_compile
from re import VERBOSE
from re import VERBOSE
# Site-Package Imports
# Site-Package Imports
# Configobj
# Configobj
from configobj import Section
from configobj import Section
# Source.Python Imports
# Source.Python Imports
# Core
# Core
from core import GameConfigObj
from core import GameConfigObj
# Paths
# Paths
from paths import TRANSLATION_PATH
from paths import TRANSLATION_PATH
from paths import GAME_PATH
from paths import GAME_PATH
# Translations
# Translations
from translations.manager import language_manager
from translations.manager import language_manager
# =============================================================================
# =============================================================================
# >> ALL DECLARATION
# >> ALL DECLARATION
# =============================================================================
# =============================================================================
__all__ = ('LangStrings',
__all__ = ('LangStrings',
'TranslationStrings',
'TranslationStrings',
)
)
# =============================================================================
# =============================================================================
# >> GLOBAL VARIABLES
# >> GLOBAL VARIABLES
# =============================================================================
# =============================================================================
# Get an re.compile instance to correct all double escaped strings
# Get an re.compile instance to correct all double escaped strings
_double_escaped_pattern = re_compile(
_double_escaped_pattern = re_compile(
r"""(\\(?:(?P<octal>[0-7]{1,3})|x(?P<hexadecimal>[0-9|a-f|A-F]{2})|
r"""(\\(?:(?P<octal>[0-7]{1,3})|x(?P<hexadecimal>[0-9|a-f|A-F]{2})|
(?P<notation>a|b|e|f|n|r|s|t|v)))""", VERBOSE)
(?P<notation>a|b|e|f|n|r|s|t|v)))""", VERBOSE)
# =============================================================================
# =============================================================================
# >> CLASSES
# >> CLASSES
# =============================================================================
# =============================================================================
class LangStrings(dict):
class LangStrings(dict):
"""Dictionary class used to store all strings for a plugin."""
"""Dictionary class used to store all strings for a plugin."""
def __init__(self, infile, encoding='utf_8'):
def __init__(self, infile, encoding='utf_8'):
"""Add all strings and fix double escaped strings."""
"""Add all strings and fix double escaped strings."""
# Initialize the dictionary
# Initialize the dictionary
super().__init__()
super().__init__()
# Get the path to the given file
# Get the path to the given file
self._mainfile = TRANSLATION_PATH / infile + '.ini'
self._mainfile = TRANSLATION_PATH / infile + '.ini'
self._encoding = encoding
self._encoding = encoding
# Does the file exist?
# Does the file exist?
if not self._mainfile.isfile():
if not self._mainfile.isfile():
# Raise an error
# Raise an error
raise FileNotFoundError(
raise FileNotFoundError(
'No file found at {0}'.format(self._mainfile))
'No file found at {0}'.format(self._mainfile))
# Get the path to the server specific file
# Get the path to the server specific file
self._serverfile = self._mainfile.parent / '{0}_server.ini'.format(
self._serverfile = self._mainfile.parent / '{0}_server.ini'.format(
self._mainfile.namebase)
self._mainfile.namebase)
# Get the strings from the main file
# Get the strings from the main file
main_strings = GameConfigObj(self._mainfile, encoding=encoding)
main_strings = GameConfigObj(self._mainfile, encoding=encoding)
# Does the server specific file exist?
# Does the server specific file exist?
if not self._serverfile.isfile() and not infile.startswith('_core/'):
if not self._serverfile.isfile() and not infile.startswith('_core/'):
# Create the server specific file
# Create the server specific file
self._create_server_file()
self._create_server_file()
# Otherwise
# Otherwise
else:
else:
# Get any strings from the server specific file
# Get any strings from the server specific file
server_strings = GameConfigObj(self._serverfile, encoding=encoding)
server_strings = GameConfigObj(self._serverfile, encoding=encoding)
# Merge the two ConfigObj instances together
# Merge the two ConfigObj instances together
main_strings.merge(server_strings)
main_strings.merge(server_strings)
# Loop through all strings
# Loop through all strings
for key in main_strings:
for key in main_strings:
# Is the current string not a Section?
# Is the current string not a Section?
if not isinstance(main_strings[key], Section):
if not isinstance(main_strings[key], Section):
# No need to go further
# No need to go further
continue
continue
# Get a TranslationStrings instance for the current string
# Get a TranslationStrings instance for the current string
translation_strings = TranslationStrings()
translation_strings = TranslationStrings()
# Loop through all languages for the current string
# Loop through all languages for the current string
for lang in main_strings[key]:
for lang in main_strings[key]:
# Get the shortname of the current language
# Get the shortname of the current language
language = language_manager.get_language(lang)
language = language_manager.get_language(lang)
# Was the language found?
# Was the language found?
if language is None:
if language is None:
# Do not add this translation
# Do not add this translation
# Possibly raise an error silently here
# Possibly raise an error silently here
continue
continue
# Get the language's string and fix any escaped strings
# Get the language's string and fix any escaped strings
translation_strings[
translation_strings[
language] = self._replace_escaped_sequences(
language] = self._replace_escaped_sequences(
main_strings[key][lang])
main_strings[key][lang])
# Add the TranslationStrings instance for the current string
# Add the TranslationStrings instance for the current string
self[key] = translation_strings
self[key] = translation_strings
# Is there any default language specified into the main file?
# Is there any default language specified into the main file?
if 'DEFAULT_LANGUAGE' in main_strings:
if 'DEFAULT_LANGUAGE' in main_strings:
# Get the default language
# Get the default language
default_language = main_strings['DEFAULT_LANGUAGE']
default_language = main_strings['DEFAULT_LANGUAGE']
# Make sure it is not a Section
# Make sure it is not a Section
if not isinstance(default_language, Section):
if not isinstance(default_language, Section):
# Get the given language code
# Get the given language code
language_code = language_manager.get_language(default_language)
language_code = language_manager.get_language(default_language)
# Is the language valid?
# Is the language valid?
if language_code is not None:
if language_code is not None:
# Set the default language
# Set the default language
self.default_language = language_code
self.default_language = language_code
# Delete the key from the main file as we are done with it
# Delete the key from the main file as we are done with it
del main_strings['DEFAULT_LANGUAGE']
del main_strings['DEFAULT_LANGUAGE']
else:
default_language = language_manager.default
self.default_language = default_language
def __setattr__(self, attribute, value):
def __setattr__(self, attribute, value):
"""Register the default language."""
"""Register the default language."""
# Is the given attribute the default language?
# Is the given attribute the default language?
if attribute == 'default_language':
if attribute == 'default_language':
# Get the given language code
# Get the given language code
language_code = language_manager.get_language(value)
language_code = language_manager.get_language(value)
# Is the given language code valid?
# Is the given language code valid?
if language_code is not None:
if language_code is not None:
# Loop through all strings
# Loop through all strings
for key in self:
for key in self:
# Set the default language to use for that string
# Set the default language to use for that string
self[key]._default_language = language_code
self[key]._default_language = language_code
# Override the given value
# Override the given value
value = language_code
value = language_code
# Set the attribute
# Set the attribute
super().__setattr__(attribute, value)
super().__setattr__(attribute, value)
def _create_server_file(self):
def _create_server_file(self):
"""Create a server specific langstrings file."""
"""Create a server specific langstrings file."""
# Get the server specific file's ConfigObj instance
# Get the server specific file's ConfigObj instance
server_file = GameConfigObj(self._serverfile, encoding=self._encoding)
server_file = GameConfigObj(self._serverfile, encoding=self._encoding)
# Set the initial comments to explain what the file is for
# Set the initial comments to explain what the file is for
server_file.initial_comment = _translation_strings[
server_file.initial_comment = _translation_strings[
'Initial Comment'].get_string(
'Initial Comment'].get_string(
language_manager.default,
language_manager.default,
filename=self._mainfile.replace(GAME_PATH, '')).splitlines()
filename=self._mainfile.replace(GAME_PATH, '')).splitlines()
# Write the server specific file
# Write the server specific file
server_file.write()
server_file.write()
@staticmethod
@staticmethod
def _replace_escaped_sequences(given_string):
def _replace_escaped_sequences(given_string):
"""Fix all double escaped strings."""
"""Fix all double escaped strings."""
# Loop through all matches
# Loop through all matches
for escaped_match in set(
for escaped_match in set(
_double_escaped_pattern.finditer(given_string)):
_double_escaped_pattern.finditer(given_string)):
# Get the match as a string
# Get the match as a string
matching_string = escaped_match.group()
matching_string = escaped_match.group()
# Get a dictionnary of all groups
# Get a dictionnary of all groups
matching_groups = escaped_match.groupdict()
matching_groups = escaped_match.groupdict()
# Are we matching any octal sequences?
# Are we matching any octal sequences?
if matching_groups['octal']:
if matching_groups['octal']:
# Replace it
# Replace it
given_string = given_string.replace(
given_string = given_string.replace(
matching_string, chr(int(matching_groups['octal'])))
matching_string, chr(int(matching_groups['octal'])))
# Otherwise, are we matching any hexadecimal sequences?
# Otherwise, are we matching any hexadecimal sequences?
elif matching_groups['hexadecimal']:
elif matching_groups['hexadecimal']:
# Replace it
# Replace it
given_string = given_string.replace(
given_string = given_string.replace(
matching_string, str(unhexlify(
matching_string, str(unhexlify(
matching_groups['hexadecimal']), encoding='ascii'))
matching_groups['hexadecimal']), encoding='ascii'))
# Otherwise, that means we are matching a notation
# Otherwise, that means we are matching a notation
else:
else:
# Replace it
# Replace it
given_string = given_string.replace(
given_string = given_string.replace(
matching_string, unicode_escape_decode(matching_string)[0])
matching_string, unicode_escape_decode(matching_string)[0])
# Return the replaced string
# Return the replaced string
return given_string
return given_string
def get_strings(self, key, **tokens):
def get_strings(self, key, **tokens):
"""Return a TranslationStrings object with updated tokens."""
"""Return a TranslationStrings object with updated tokens."""
strings = self[key]
strings = self[key]
strings.tokens.update(tokens)
strings.tokens.update(tokens)
return strings
return strings
def make_strings(self, string, **tokens):
s = TranslationStrings()
s[self.default_language] = string
s.tokens.update(tokens)
return s
class TranslationStrings(dict):
class TranslationStrings(dict):
"""Stores and get language strings for a particular string."""
"""Stores and get language strings for a particular string."""
def __init__(self):
def __init__(self):
"""Store an empty dictionary as the tokens."""
"""Store an empty dictionary as the tokens."""
super().__init__()
super().__init__()
self.tokens = {}
self.tokens = {}
def get_string(self, language=None, **tokens):
def get_string(self, language=None, **tokens):
"""Return the language string for the given language/tokens."""
"""Return the language string for the given language/tokens."""
requested_language = language
# Was no language passed?
# Was no language passed?
if language is None:
if language is None:
# Set the language to the server's default
# Set the language to the server's default
language = language_manager.default
language = language_manager.default
# Get the language shortname to be used
# Get the language shortname to be used
language = self.get_language(language)
language = self.get_language(language)
# Was a valid language found?
# Was a valid language found?
if language is None:
if language is None:
# Return an empty string
# Return an empty string
# Possibly raise an error silently here
# Possibly raise an error silently here
return ''
return ''
# Expose all TranslationStrings instances in self.tokens
# Expose all TranslationStrings instances in self.tokens
exposed_tokens = {}
exposed_tokens = {}
# Pass additional kwargs - these will be used to format the string
# Pass additional kwargs - these will be used to format the string
self._update_exposed_tokens(
self._update_exposed_tokens(
exposed_tokens, language, self.tokens, **tokens)
exposed_tokens, requested_language, self.tokens, **tokens)
# Don't pass any additional kwargs, each token should either
# Don't pass any additional kwargs, each token should either
# be trivial or rely on itself (self.tokens)
# be trivial or rely on itself (self.tokens)
self._update_exposed_tokens(exposed_tokens, language, tokens)
self._update_exposed_tokens(exposed_tokens, requested_language, tokens)
# Return the formatted message
# Return the formatted message
return self[language].format(**exposed_tokens)
return self[language].format(**exposed_tokens)
@staticmethod
@staticmethod
def _update_exposed_tokens(exposed_tokens, language, tokens, **kwargs):
def _update_exposed_tokens(exposed_tokens, language, tokens, **kwargs):
for token_name, token in tokens.items():
for token_name, token in tokens.items():
if isinstance(token, TranslationStrings):
if isinstance(token, TranslationStrings):
token = token.get_string(language, **kwargs)
token = token.get_string(language, **kwargs)
exposed_tokens[token_name] = token
exposed_tokens[token_name] = token
def get_language(self, language):
def get_language(self, language):
"""Return the language to be used."""
"""Return the language to be used."""
# Get the given language's shortname
# Get the given language's shortname
language = language_manager.get_language(language)
language = language_manager.get_language(language)
# Was a language found?
# Was a language found?
if language is not None and language in self:
if language is not None and language in self:
# Return the language
# Return the language
return language
return language
# Is the server's default language in the dictionary?
# Is the server's default language in the dictionary?
if language_manager.default in self:
if language_manager.default in self:
# Return the server's default language
# Return the server's default language
return language_manager.default
return language_manager.default
# Is there any default language defined?
# Is there any default language defined?
if hasattr(self, '_default_language'):
if hasattr(self, '_default_language'):
# Is the default language available for that string?
# Is the default language available for that string?
if self._default_language in self:
if self._default_language in self:
# Return the default language to use
# Return the default language to use
return self._default_language
return self._default_language
# Is the server's fallback language in the dictionary?
# Is the server's fallback language in the dictionary?
if language_manager.fallback in self:
if language_manager.fallback in self:
# Return the server's fallback language
# Return the server's fallback language
return language_manager.fallback
return language_manager.fallback
# Return None as the language, as no language has been found
# Return None as the language, as no language has been found
return None
return None
def tokenized(self, **tokens):
def tokenized(self, **tokens):
"""Create a new TranslationStrings instance and store tokens in it.
"""Create a new TranslationStrings instance and store tokens in it.
:param dict tokens: Tokens to store in the instance.
:param dict tokens: Tokens to store in the instance.
:return: New TranslationStrings instance with tokens stored in it.
:return: New TranslationStrings instance with tokens stored in it.
:rtype: TranslationStrings
:rtype: TranslationStrings
"""
"""
result = TranslationStrings()
result = TranslationStrings()
result.tokens.update(tokens)
result.tokens.update(tokens)
result.update(self)
result.update(self)
return result
return result
# Get the translations language strings
# Get the translations language strings
_translation_strings = LangStrings('_core/translations_strings')
_translation_strings = LangStrings('_core/translations_strings')