#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Internationalization (i18n) utilities.
"""
# Copyright 2006-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.
import gettext
from locale import getlocale, Error, LC_MESSAGES
import re
from typing import Optional, Text  # noqa: F401
import six
[docs]class I18N_Error(Exception):
	"""
	Error in Internationalization.
	"""
	pass 
[docs]class Locale(object):
	"""
	Represents a locale specification and provides simple access to
	language, territory, codeset and modifier.
	:param locale: The locale string `language[_territory][.codeset][@modifier]`.
	:type locale: str or None
	>>> Locale("deu_GER")
	>>> str(Locale("ca_ES@valencia"))
	>>> str(Locale(""))
	"""
	REGEX = re.compile(
		r'^'
		r'(?P<language>([a-z]{2}|C|POSIX))'
		r'(?:_(?P<territory>[A-Z]{2}))?'
		r'(?:\.(?P<codeset>[a-zA-Z-0-9]+))?'
		r'(?:@(?P<modifier>.+))?'
		r'$')
	def __init__(self, locale=None):
		# type: (Optional[str]) -> None
		self.__reset()
		if locale is not None:
			self.parse(locale)
	def __reset(self):
		# type: () -> None
		self.language = ""
		self.territory = ""
		self.codeset = ""
		self.modifier = ""
[docs]	def parse(self, locale):
		# type: (str) -> None
		"""
		Parse locale string.
		:param str locale: The locale string `language[_territory][.codeset][@modifier]`.
		:raises TypeError: if `locale` is not a string.
		:raises I18N_Error: if `locale` does not match the format.
		"""
		if not isinstance(locale, six.string_types):
			raise TypeError('locale must be of type string')
		self.__reset()
		regex = Locale.REGEX.match(locale)
		if not regex:
			raise I18N_Error('attribute does not match locale specification language[_territory][.codeset][@modifier]')
		self.codeset = 'UTF-8'  # default encoding
		for key, value in regex.groupdict().items():
			if value is None:
				continue
			setattr(self, key, value) 
	def __bool__(self):
		# type: () -> bool
		return bool(self.language)
	__nonzero__ = __bool__
	def __str__(self):
		# type: () -> str
		text = self.language or ''
		if self.language not in ('C', 'POSIX'):
			if self.territory:
				text += '_%s' % self.territory
		if self.codeset:
			text += '.%s' % self.codeset
		if self.modifier:
			text += '@%s' % self.modifier
		return text 
[docs]class NullTranslation(object):
	"""
	Dummy translation.
	:param str namespace: The name of the translation domain.
	:param str locale_spec: The selected locale.
	:param str localedir: The name of the directory containing the translation files.
	"""
	def __init__(self, namespace, locale_spec=None, localedir=None):
		# type: (str, Optional[str], Optional[str]) -> None
		self._set_domain(namespace)  # type: Optional[str]
		self._translation = None  # type: Optional[gettext.NullTranslations]
		self._localedir = localedir  # type: Optional[str]
		self._localespec = None  # type: Optional[Locale]
		self._locale = locale_spec  # type: Optional[str]
		if not self._locale:
			self.set_language()
	def _set_domain(self, namespace):
		# type: (str) -> None
		"""
		Select translation domain.
		:param str namespace: The name of the translation domain.
		"""
		if namespace is not None:
			self._domain = namespace.replace('/', '-').replace('.', '-')
		else:
			self._domain = None
	domain = property(fset=_set_domain)
[docs]	def set_language(self, language=""):
		# type: (str) -> None
		"""
		Select language.
		:param str language: The language code.
		"""
		pass 
	def _get_locale(self):
		# type: () -> Optional[Locale]
		"""
		Return currently selected locale.
		:returns: The currently selected locale.
		:rtype: Locale
		"""
		return self._localespec
	def _set_locale(self, locale_spec=None):
		# type: (Optional[str]) -> None
		"""
		Select new locale.
		:param str locale_spec: The new locale specification.
		"""
		if locale_spec is None:
			return
		self._localespec = Locale(locale_spec)
	locale = property(fget=_get_locale, fset=_set_locale)
[docs]	def translate(self, message):
		# type: (str) -> Text
		"""
		Translate message.
		:param str message: The message to translate.
		:returns: The localized message.
		:rtype: str
		"""
		if self._translation is None:
			return message
		if six.PY2:
			return self._translation.ugettext(message)
		return self._translation.gettext(message) 
	_ = translate 
[docs]class Translation(NullTranslation):
	"""
	Translation.
	"""
	locale = Locale()  # type: Locale # type: ignore
[docs]	def set_language(self, language=""):
		# type: (str) -> None
		"""
		Select language.
		:param str language: The language code.
		:raises I18N_Error: if the given locale is not valid.
		"""
		if language:
			Translation.locale.parse(language)
		if not Translation.locale:
			try:
				lang = getlocale(LC_MESSAGES)
				language = lang[0] or "C"
				Translation.locale.parse(language)
			except Error as exc:
				raise I18N_Error('The given locale is not valid: %s' % (exc,))
		if not self._domain:
			return
		try:
			self._translation = gettext.translation(self._domain, languages=(Translation.locale.language, ), localedir=self._localedir)
		except IOError:
			try:
				self._translation = gettext.translation(self._domain, languages=('%s_%s' % (Translation.locale.language, Translation.locale.territory), ), localedir=self._localedir)
			except IOError:
				self._translation = None