From be84468d3f34105f8b361621696d07fd7a5c00ff Mon Sep 17 00:00:00 2001 From: Vassilii Khachaturov Date: Wed, 16 Oct 2013 14:04:21 +0000 Subject: [PATCH] 6926: merge to trunk p2: gramps/ code changes svn: r23324 --- gramps/gen/datehandler/__init__.py | 18 +- gramps/gen/datehandler/_date_fr.py | 261 ++++------ gramps/gen/datehandler/_date_hr.py | 284 +---------- gramps/gen/datehandler/_date_ru.py | 267 +--------- gramps/gen/datehandler/_datedisplay.py | 457 ++++++++++++------ gramps/gen/datehandler/_datehandler.py | 6 + gramps/gen/datehandler/_dateparser.py | 152 ++++-- gramps/gen/datehandler/_datestrings.py | 346 +++++++++++++ gramps/gen/datehandler/_grampslocale.py | 63 +-- .../gen/datehandler/test/datedisplay_test.py | 119 +++++ .../gen/datehandler/test/dateparser_test.py | 128 +++++ .../gen/datehandler/test/datestrings_test.py | 80 +++ gramps/gen/lib/date.py | 2 +- gramps/gen/utils/grampslocale.py | 156 +++++- gramps/gen/utils/test/grampslocale_test.py | 126 +++++ gramps/plugins/drawreport/calendarreport.py | 8 +- gramps/plugins/drawreport/timeline.py | 6 +- gramps/plugins/webreport/webcal.py | 5 +- 18 files changed, 1534 insertions(+), 950 deletions(-) create mode 100644 gramps/gen/datehandler/_datestrings.py create mode 100644 gramps/gen/datehandler/test/datedisplay_test.py create mode 100644 gramps/gen/datehandler/test/dateparser_test.py create mode 100644 gramps/gen/datehandler/test/datestrings_test.py create mode 100644 gramps/gen/utils/test/grampslocale_test.py diff --git a/gramps/gen/datehandler/__init__.py b/gramps/gen/datehandler/__init__.py index cda2d44fd..51101fdd8 100644 --- a/gramps/gen/datehandler/__init__.py +++ b/gramps/gen/datehandler/__init__.py @@ -24,6 +24,8 @@ Class handling language-specific selection for date parser and displayer. """ +from __future__ import print_function, unicode_literals + #------------------------------------------------------------------------- # # set up logging @@ -36,6 +38,7 @@ _ = glocale.translation.sgettext # import prerequisites for localized handlers from ._datehandler import (LANG, LANG_SHORT, LANG_TO_PARSER, LANG_TO_DISPLAY, register_datehandler) +from . import _datestrings # Import all the localized handlers from . import _date_ar @@ -91,5 +94,16 @@ except: # Import utility functions from ._dateutils import * -from ._grampslocale import (codeset, month_to_int, long_months, short_months, - long_days, short_days, tformat) +from ._grampslocale import (codeset, long_days, short_days, tformat) + +if __name__ == "__main__": + from ._datedisplay import DateDisplay + m = 0 + for l,d in LANG_TO_DISPLAY.items(): + if len(l) != 2: + continue + m = max(m, len(d.formats)) + print("{}: {} {} own dg: {}".format( + l, len(d.formats), d.formats, + d._display_gregorian != DateDisplay._display_gregorian)) + print("MAX: ", m) diff --git a/gramps/gen/datehandler/_date_fr.py b/gramps/gen/datehandler/_date_fr.py index eb2fea214..b030b17a1 100644 --- a/gramps/gen/datehandler/_date_fr.py +++ b/gramps/gen/datehandler/_date_fr.py @@ -21,7 +21,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# $Id$ +# $Id: _date_fr.py 22672 2013-07-13 18:01:08Z paul-franklin $ #------------------------------------------------------------------------- # @@ -60,92 +60,15 @@ class DateParserFR(DateParser): month_to_int = DateParser.month_to_int - # Custom short months not the same as long months - - month_to_int["janv"] = 1 - month_to_int["févr"] = 2 - month_to_int["juil"] = 7 - month_to_int["sept"] = 9 - month_to_int["oct"] = 10 - month_to_int["déc"] = 12 - - # Add common value - - month_to_int["bluviose"] = 5 - month_to_int["vendose"] = 6 - month_to_int["7bre"] = 9 - month_to_int["8bre"] = 10 - month_to_int["9bre"] = 11 - month_to_int["10bre"] = 12 - month_to_int["xbre"] = 12 - - # Add common latin - - month_to_int["januaris"] = 1 - month_to_int["januarii"] = 1 - month_to_int["januarius"] = 1 - month_to_int["februaris"] = 2 - month_to_int["februarii"] = 2 - month_to_int["februarius"] = 2 - month_to_int["martii"] = 3 - month_to_int["martius"] = 3 - month_to_int["aprilis"] = 4 - month_to_int["maius"] = 5 - month_to_int["maii"] = 5 - month_to_int["junius"] = 6 - month_to_int["junii"] = 6 - month_to_int["julius"] = 7 - month_to_int["julii"] = 7 - month_to_int["augustus"] = 8 - month_to_int["augusti"] = 8 - month_to_int["septembris"] = 9 - month_to_int["7bris"] = 9 - month_to_int["september"] = 9 - month_to_int["october"] = 10 - month_to_int["octobris"] = 10 - month_to_int["8bris"] = 10 - month_to_int["novembris"] = 11 - month_to_int["9bris"] = 11 - month_to_int["november"] = 11 - month_to_int["decembris"] = 12 - month_to_int["10bris"] = 12 - month_to_int["xbris"] = 12 - month_to_int["december"] = 12 - - #local and historical variants - # Add common on east france - - month_to_int["janer"] = 1 - month_to_int["jenner"] = 1 - month_to_int["hartmonat"] = 1 - month_to_int["hartung"] = 1 - month_to_int["eismond"] = 1 - month_to_int["hornung"] = 2 - month_to_int["wintermonat"] = 2 - month_to_int["taumond"] = 2 - month_to_int["narrenmond"] = 2 - month_to_int["lenzing"] = 3 - month_to_int["ostermond"] = 4 - month_to_int["wonnemond"] = 5 - month_to_int["wiesenmonat"] = 5 - month_to_int["brachet"] = 6 - month_to_int["heuet"] = 7 - month_to_int["ernting"] = 8 - month_to_int["scheiding"] = 9 - month_to_int["gilbhard"] = 10 - month_to_int["nebelmonat"] = 11 - month_to_int["nebelung"] = 11 - month_to_int["julmond"] = 12 - modifier_to_int = { - 'avant': Date.MOD_BEFORE, - 'av.' : Date.MOD_BEFORE, - #u'av' : Date.MOD_BEFORE, # Broke Hebrew "Av" month name - #u'<' : Date.MOD_BEFORE, # Worrying about XML/HTML parsing - 'après': Date.MOD_AFTER, - 'ap.' : Date.MOD_AFTER, - 'ap' : Date.MOD_AFTER, - #u'>' : Date.MOD_AFTER, # Worrying about XML/HTML parsing + 'avant' : Date.MOD_BEFORE, + 'av.' : Date.MOD_BEFORE, + #u'av' : Date.MOD_BEFORE, # Broke Hebrew "Av" month name + #u'<' : Date.MOD_BEFORE, # Worrying about XML/HTML parsing + 'après' : Date.MOD_AFTER, + 'ap.' : Date.MOD_AFTER, + 'ap' : Date.MOD_AFTER, + #u'>' : Date.MOD_AFTER, # Worrying about XML/HTML parsing 'environ' : Date.MOD_ABOUT, 'env.' : Date.MOD_ABOUT, 'env' : Date.MOD_ABOUT, @@ -156,24 +79,7 @@ class DateParserFR(DateParser): 'vers' : Date.MOD_ABOUT, '~' : Date.MOD_ABOUT, } - - calendar_to_int = { - 'grégorien': Date.CAL_GREGORIAN, - 'g' : Date.CAL_GREGORIAN, - 'julien': Date.CAL_JULIAN, - 'j' : Date.CAL_JULIAN, - 'hébreu': Date.CAL_HEBREW, - 'h' : Date.CAL_HEBREW, - 'islamique': Date.CAL_ISLAMIC, - 'i' : Date.CAL_ISLAMIC, - 'révolutionnaire': Date.CAL_FRENCH, - 'r' : Date.CAL_FRENCH, - 'perse': Date.CAL_PERSIAN, - 'p' : Date.CAL_PERSIAN, - 'suédois': Date.CAL_SWEDISH, - 's' : Date.CAL_SWEDISH, - } - + quality_to_int = { 'estimée': Date.QUAL_ESTIMATED, 'est.' : Date.QUAL_ESTIMATED, @@ -201,7 +107,84 @@ class DateParserFR(DateParser): override stuff from this method. See DateParserRU() as an example. """ DateParser.init_strings(self) - + + DateParser.calendar_to_int.update({ + 'grégorien' : Date.CAL_GREGORIAN, + 'g' : Date.CAL_GREGORIAN, + 'julien' : Date.CAL_JULIAN, + 'j' : Date.CAL_JULIAN, + 'hébreu' : Date.CAL_HEBREW, + 'h' : Date.CAL_HEBREW, + 'islamique' : Date.CAL_ISLAMIC, + 'i' : Date.CAL_ISLAMIC, + 'révolutionnaire' : Date.CAL_FRENCH, + 'r' : Date.CAL_FRENCH, + 'perse' : Date.CAL_PERSIAN, + 'p' : Date.CAL_PERSIAN, + 'suédois' : Date.CAL_SWEDISH, + 's' : Date.CAL_SWEDISH, + }) + + DateParser.month_to_int.update({ + "januaris" : 1, + "januarii" : 1, + "januarius" : 1, + "janer" : 1, + "jenner" : 1, + "hartmonat" : 1, + "hartung" : 1, + "eismond" : 1, + "februaris" : 2, + "februarii" : 2, + "februarius" : 2, + "hornung" : 2, + "wintermonat" : 2, + "taumond" : 2, + "narrenmond" : 2, + "martii" : 3, + "martius" : 3, + "lenzing" : 3, + "aprilis" : 4, + "ostermond" : 4, + "maius" : 5, + "maii" : 5, + "bluviose" : 5, + "wonnemond" : 5, + "wiesenmonat" : 5, + "junius" : 6, + "junii" : 6, + "vendose" : 6, + "brachet" : 6, + "julius" : 7, + "julii" : 7, + "heuet" : 7, + "augustus" : 8, + "augusti" : 8, + "ernting" : 8, + "septembris" : 9, + "7bre" : 9, + "7bris" : 9, + "september" : 9, + "october" : 10, + "octobris" : 10, + "8bre" : 10, + "8bris" : 10, + "gilbhard" : 10, + "november" : 11, + "novembris" : 11, + "9bre" : 11, + "9bris" : 11, + "nebelmonat" : 11, + "nebelung" : 11, + "december" : 12, + "decembris" : 12, + "10bre" : 12, + "10bris" : 12, + "xbre" : 12, + "xbris" : 12, + "julmond" : 12, + }) + # This self._numeric is different from the base # avoid bug gregorian / french calendar conversion (+/-10 days) @@ -211,35 +194,35 @@ class DateParserFR(DateParser): self._range = re.compile("(entre|ent\.|ent)\s+(?P.+)\s+(et)\s+(?P.+)", re.IGNORECASE) - # This self._text are different from the base - # by adding ".?" after the first date and removing "\s*$" at the end + # This self._text are different from the base + # by adding ".?" after the first date and removing "\s*$" at the end - #gregorian and julian + #gregorian and julian self._text2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._mon_str, re.IGNORECASE) - #hebrew + #hebrew self._jtext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._jmon_str, re.IGNORECASE) - #french + #french self._ftext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._fmon_str, re.IGNORECASE) - #persian + #persian self._ptext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._pmon_str, re.IGNORECASE) - #islamic + #islamic self._itext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._imon_str, re.IGNORECASE) - #swedish + #swedish self._stext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?' % self._smon_str, re.IGNORECASE) @@ -253,19 +236,6 @@ class DateDisplayFR(DateDisplay): """ French language date display class. """ - long_months = ( "", "janvier", "février", "mars", "avril", "mai", - "juin", "juillet", "août", "septembre", "octobre", - "novembre", "décembre" ) - - short_months = ( "", "janv", "févr", "mars", "avril", "mai", "juin", - "juil", "août", "sept", "oct", "nov", "déc" ) - - calendar = ("", "Julien", "Hébreu", "Révolutionnaire", - "Perse", "Islamique", "Suédois") - - _mod_str = ("", "avant ", "après ", "vers ", "", "", "") - - _qual_str = ("", "estimée ", "calculée ", "") _bce_str = "%s avant le calendrier" @@ -287,7 +257,7 @@ class DateDisplayFR(DateDisplay): ) # this definition must agree with its "_display_gregorian" method - def _display_gregorian(self, date_val): + def _display_gregorian(self, date_val, **kwargs): """ display gregorian calendar date in different format """ @@ -408,41 +378,8 @@ class DateDisplayFR(DateDisplay): return self._bce_str % value else: return value - - def display(self, date): - """ - Return a text string representing the date. - """ - - mod = date.get_modifier() - cal = date.get_calendar() - qual = date.get_quality() - start = date.get_start_date() - newyear = date.get_new_year() - - qual_str = (self._qual_str)[qual] - - if mod == Date.MOD_TEXTONLY: - return date.get_text() - elif start == Date.EMPTY: - return "" - elif mod == Date.MOD_SPAN: - date1 = self.display_cal[cal](start) - date2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%s%s %s %s %s%s" % (qual_str, 'de', date1, 'à', - date2, scal) - elif mod == Date.MOD_RANGE: - date1 = self.display_cal[cal](start) - date2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%s%s %s %s %s%s" % (qual_str, 'entre', date1, 'et', - date2, scal) - else: - text = self.display_cal[date.get_calendar()](start) - scal = self.format_extras(cal, newyear) - return "%s%s%s%s" % (qual_str, (self._mod_str)[mod], text, - scal) + + display = DateDisplay.display_formatted #------------------------------------------------------------------------- # diff --git a/gramps/gen/datehandler/_date_hr.py b/gramps/gen/datehandler/_date_hr.py index 9b73f56d4..8272fcbcf 100644 --- a/gramps/gen/datehandler/_date_hr.py +++ b/gramps/gen/datehandler/_date_hr.py @@ -18,7 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# $Id$ +# $Id: _date_hr.py 22672 2013-07-13 18:01:08Z paul-franklin $ # # Croatian version 2008 by Josip @@ -50,98 +50,6 @@ from ._datehandler import register_datehandler # #------------------------------------------------------------------------- class DateParserHR(DateParser): - """ - Converts a text string into a Date object - """ - month_to_int = DateParser.month_to_int - - month_to_int["siječanj"] = 1 - month_to_int["siječnja"] = 1 - month_to_int["sij"] = 1 - month_to_int["januar"] = 1 - month_to_int["januara"] = 1 - month_to_int["i"] = 1 - - month_to_int["veljača"] = 2 - month_to_int["veljače"] = 2 - month_to_int["velj"] = 2 - month_to_int["februar"] = 2 - month_to_int["februara"] = 2 - month_to_int["ii"] = 2 - - month_to_int["ožujak"] = 3 - month_to_int["ožujka"] = 3 - month_to_int["ožu"] = 3 - month_to_int["mart"] = 3 - month_to_int["marta"] = 3 - month_to_int["iii"] = 3 - - month_to_int["travanj"] = 4 - month_to_int["travnja"] = 4 - month_to_int["tra"] = 4 - month_to_int["april"] = 4 - month_to_int["aprila"] = 4 - month_to_int["iv"] = 4 - - month_to_int["svibanj"] = 5 - month_to_int["svibnja"] = 5 - month_to_int["svi"] = 5 - month_to_int["maj"] = 5 - month_to_int["maja"] = 5 - month_to_int["v"] = 5 - - month_to_int["lipanj"] = 6 - month_to_int["lipnja"] = 6 - month_to_int["lip"] = 6 - month_to_int["jun"] = 6 - month_to_int["juna"] = 6 - month_to_int["vi"] = 6 - - month_to_int["srpanj"] = 7 - month_to_int["srpnja"] = 7 - month_to_int["srp"] = 7 - month_to_int["juli"] = 7 - month_to_int["jula"] = 7 - month_to_int["vii"] = 7 - - month_to_int["kolovoz"] = 8 - month_to_int["kolovoza"] = 8 - month_to_int["kol"] = 8 - month_to_int["august"] = 8 - month_to_int["augusta"] = 8 - month_to_int["viii"] = 8 - - month_to_int["rujan"] = 9 - month_to_int["rujna"] = 9 - month_to_int["ruj"] = 9 - month_to_int["septembar"] = 9 - month_to_int["septembra"] = 9 - month_to_int["ix"] = 9 - month_to_int["7ber"] = 9 - - month_to_int["listopad"] = 10 - month_to_int["listopada"] = 10 - month_to_int["lis"] = 10 - month_to_int["oktobar"] = 10 - month_to_int["oktobra"] = 10 - month_to_int["x"] = 10 - month_to_int["8ber"] = 10 - - month_to_int["studeni"] = 11 - month_to_int["studenog"] = 11 - month_to_int["stu"] = 11 - month_to_int["novembar"] = 11 - month_to_int["novembra"] = 11 - month_to_int["xi"] = 11 - month_to_int["9ber"] = 11 - - month_to_int["prosinac"] = 12 - month_to_int["prosinca"] = 12 - month_to_int["pro"] = 12 - month_to_int["decembar"] = 12 - month_to_int["decembra"] = 12 - month_to_int["xii"] = 12 - modifier_to_int = { 'prije' : Date.MOD_BEFORE, 'pr. ' : Date.MOD_BEFORE, @@ -152,23 +60,6 @@ class DateParserHR(DateParser): } - calendar_to_int = { - 'gregorijanski' : Date.CAL_GREGORIAN, - 'greg.' : Date.CAL_GREGORIAN, - 'julijanski' : Date.CAL_JULIAN, - 'jul.' : Date.CAL_JULIAN, - 'hebrejski' : Date.CAL_HEBREW, - 'hebr.' : Date.CAL_HEBREW, - 'islamski' : Date.CAL_ISLAMIC, - 'isl.' : Date.CAL_ISLAMIC, - 'francuski republikanski': Date.CAL_FRENCH, - 'franc.' : Date.CAL_FRENCH, - 'perzijski' : Date.CAL_PERSIAN, - 'perz. ' : Date.CAL_PERSIAN, - 'švedski' : Date.CAL_SWEDISH, - 's' : Date.CAL_SWEDISH, - } - quality_to_int = { 'približno' : Date.QUAL_ESTIMATED, 'prb.' : Date.QUAL_ESTIMATED, @@ -184,20 +75,20 @@ class DateParserHR(DateParser): compiles regular expression strings for matching dates """ DateParser.init_strings(self) - # match 'Day. MONTH year.' format with or without dots - self._text2 = re.compile('(\d+)?\.?\s*?%s\.?\s*((\d+)(/\d+)?)?\s*\.?$' - % self._mon_str, re.IGNORECASE) - # match Day.Month.Year. - self._numeric = re.compile( - "((\d+)[/\. ])?\s*((\d+)[/\.])?\s*(\d+)\.?$" - ) - self._span = re.compile("(od)\s+(?P.+)\s+(do)\s+(?P.+)", - re.IGNORECASE) - self._range = re.compile( - "(između)\s+(?P.+)\s+(i)\s+(?P.+)", - re.IGNORECASE) - self._jtext2 = re.compile('(\d+)?.?\s+?%s\s*((\d+)(/\d+)?)?'\ - % self._jmon_str, re.IGNORECASE) + #~ DateParser.calendar_to_int.update({ + #~ 'персидский' : Date.CAL_PERSIAN, + #~ 'п' : Date.CAL_PERSIAN, + #~ }) + _span_1 = ['od'] + _span_2 = ['do'] + _range_1 = ['između'] + _range_2 = ['i'] + self._span = re.compile("(%s)\s+(?P.+)\s+(%s)\s+(?P.+)" % + ('|'.join(_span_1), '|'.join(_span_2)), + re.IGNORECASE) + self._range = re.compile("(%s)\s+(?P.+)\s+(%s)\s+(?P.+)" % + ('|'.join(_range_1), '|'.join(_range_2)), + re.IGNORECASE) #------------------------------------------------------------------------- # @@ -208,150 +99,11 @@ class DateDisplayHR(DateDisplay): """ Croatian language date display class. """ - long_months = ( "", - "siječnja", - "veljače", - "ožujka", - "travnja", - "svibnja", - "lipnja", - "srpnja", - "kolovoza", - "rujna", - "listopada", - "studenog", - "prosinca" - ) - - #currently unused - short_months = ( "", "sij", "velj", "ožu", "tra", "svi", "lip", - "srp", "kol", "ruj", "lis", "stu", "pro" - ) - - calendar = ( - "", "julijanski", "hebrejski", - "francuski republikanski", "perzijski", "islamski", - "swedish" - ) - - _mod_str = ("", "prije ", "poslije ", "okolo ", "", "", "") - - _qual_str = ("", "približno ", "izračunato ") - + # TODO fix BUG 7064: non-Gregorian calendars wrongly use BCE notation for negative dates + # not refactoring _bce_str into base class because it'll be gone under #7064 _bce_str = "%s p.n.e." - formats = ( - "GGGG-MM-DD (ISO-8601)", - "Numerički", - "D.M.GGGG.", - "D. MMMM GGGG.", - "D. Rb GGGG." - ) - # this definition must agree with its "_display_gregorian" method - - roman_months = ( - "", - "I", - "II", - "III", - "IV", - "V", - "VI", - "VII", - "VIII", - "IX", - "X", - "XI", - "XII" - ) - - def _display_gregorian(self, date_val): - """ - display gregorian calendar date in different format - """ - # this must agree with its locale-specific "formats" definition - year = self._slash_year(date_val[2], date_val[3]) - if self.format == 0: - return self.display_iso(date_val) - elif self.format == 1: - # numerical - if date_val[3]: - return self.display_iso(date_val) - else: - if date_val[0] == 0 and date_val[1] == 0: - value = str(date_val[2]) - else: - value = self._tformat.replace('%m', str(date_val[1])) - value = value.replace('%d', str(date_val[0])) - value = value.replace('%Y', str(abs(date_val[2]))) - value = value.replace('-', '/') - elif self.format == 2: - # day.month_number.year. - if date_val[0] == 0: - if date_val[1] == 0: - value = year - else: - value = "%s.%s." % (date_val[1], year) - else: - value = "%s.%d.%s." % (date_val[0], date_val[1], year) - elif self.format == 3: - # day. month_name year. - if date_val[0] == 0: - if date_val[1] == 0: - value = "%s." % year - else: - value = "%s %s." % (self.long_months[date_val[1]], year) - else: - value = "%d. %s %s." % (date_val[0], - self.long_months[date_val[1]], year) - else: - # day. Roman_number_month year. - if date_val[0] == 0: - if date_val[1] == 0: - value = "%s." % year - else: - value = "%s %s." % (self.roman_months[date_val[1]], year) - else: - value = "%d. %s %s." % (date_val[0], - self.roman_months[date_val[1]], year) - if date_val[2] < 0: - return self._bce_str % value - else: - return value - - def display(self, date): - """ - Return a text string representing the date. - """ - mod = date.get_modifier() - cal = date.get_calendar() - qual = date.get_quality() - start = date.get_start_date() - newyear = date.get_new_year() - - qual_str = self._qual_str[qual] - - if mod == Date.MOD_TEXTONLY: - return date.get_text() - elif start == Date.EMPTY: - return "" - elif mod == Date.MOD_SPAN: - d_1 = self.display_cal[cal](start) - d_2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%s%s %s %s %s%s" % (qual_str, 'od', d_1, 'do', d_2, - scal) - elif mod == Date.MOD_RANGE: - d_1 = self.display_cal[cal](start) - d_2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%s%s %s %s %s%s" % (qual_str, 'između', d_1, 'i', d_2, - scal) - else: - text = self.display_cal[date.get_calendar()](start) - scal = self.format_extras(cal, newyear) - return "%s%s%s%s" % (qual_str, self._mod_str[mod], text, - scal) + display = DateDisplay.display_formatted #------------------------------------------------------------------------- # diff --git a/gramps/gen/datehandler/_date_ru.py b/gramps/gen/datehandler/_date_ru.py index f43aa52e3..d19907394 100644 --- a/gramps/gen/datehandler/_date_ru.py +++ b/gramps/gen/datehandler/_date_ru.py @@ -48,7 +48,6 @@ from ._datehandler import register_datehandler # #------------------------------------------------------------------------- class DateParserRU(DateParser): - modifier_to_int = { 'перед' : Date.MOD_BEFORE, 'по' : Date.MOD_BEFORE, @@ -70,22 +69,6 @@ class DateParserRU(DateParser): 'прибл' : Date.MOD_ABOUT, } - calendar_to_int = { - 'григорианский' : Date.CAL_GREGORIAN, - 'г' : Date.CAL_GREGORIAN, - 'юлианский' : Date.CAL_JULIAN, - 'ю' : Date.CAL_JULIAN, - 'еврейский' : Date.CAL_HEBREW, - 'е' : Date.CAL_HEBREW, - 'исламский' : Date.CAL_ISLAMIC, - 'и' : Date.CAL_ISLAMIC, - 'республиканский': Date.CAL_FRENCH, - 'р' : Date.CAL_FRENCH, - 'персидский' : Date.CAL_PERSIAN, - 'п' : Date.CAL_PERSIAN, - 'swedish' : Date.CAL_SWEDISH, - 's' : Date.CAL_SWEDISH, - } quality_to_int = { 'оценено' : Date.QUAL_ESTIMATED, @@ -100,127 +83,19 @@ class DateParserRU(DateParser): 'выч' : Date.QUAL_CALCULATED, } - hebrew_to_int = { - "тишрей":1, - "тишрея":1, - "хешван":2, - "хешвана":2, - "кислев":3, - "кислева":3, - "тевет":4, - "тевета":4, - "шеват":5, - "шевата":5, - "адар":6, - "адара":6, - "адара бет":7, - "нисан":8, - "нисана":8, - "ниссан":8, - "ниссана":8, - "ияр":9, - "ияра":9, - "сиван":10, - "сивана":10, - "тамуз":11, - "тамуза":11, - "таммуз":11, - "таммуза":11, - "ав":12, - "ава":12, - "элул":13, - "элула":13, - "элуль":13, - "элуля":13, - } - - islamic_to_int = { - "мухаррам":1, - "мухаррама":1, - "сафар":2, - "сафара":2, - "раби-аль-авваль":3, - "раби-аль-авваля":3, - "раби-ассани":4, - "джумада-аль-уля":5, - "джумада-аль-ахира":6, - "раджаб":7, - "раджаба":7, - "шаабан":8, - "шаабана":8, - "рамадан":9, - "рамадана":9, - "шавваль":10, - "шавваля":10, - "зуль-каада":11, - "зуль-хиджжа":12, - } - - persian_to_int = { - "фарвардин":1, - "фарвардина":1, - "урдбихишт":2, - "урдбихишта":2, - "хурдад":3, - "хурдада":3, - "тир":4, - "тира":4, - "мурдад":5, - "мурдада":5, - "шахривар":6, - "шахривара":6, - "михр":7, - "михра":7, - "абан":8, - "абана":8, - "азар":9, - "азара":9, - "дай":10, - "дая":10, - "бахман":11, - "бахмана":11, - "исфаидармуз":12, - "исфаидармуза":12, - } - - french_to_int = { - "вандемьер":1, - "вандемьера":1, - "брюмер":2, - "брюмера":2, - "фример":3, - "фримера":3, - "нивоз":4, - "нивоза":4, - "плювиоз":5, - "плювиоза":5, - "вантоз":6, - "вантоза":6, - "жерминаль":7, - "жерминаля":7, - "флореаль":8, - "флореаля":8, - "прериаль":9, - "прериаля":9, - "мессидор":10, - "мессидора":10, - "термидор":11, - "термидора":11, - "фрюктидор":12, - "фрюктидора":12, - "доп.":13, - "дополн.":13, - "дополнит.":13, - } - bce = [ 'до нашей эры', 'до н. э.', 'до н.э.', 'до н э', 'до нэ'] + DateParser.bce def init_strings(self): DateParser.init_strings(self) + DateParser.calendar_to_int.update({ + 'персидский' : Date.CAL_PERSIAN, + 'п' : Date.CAL_PERSIAN, + }) _span_1 = ['с', 'от'] - _span_2 = ['по', 'до'] + #_span_2 = ['по', 'до'] # <-- clashes with bce parsing :-( + _span_2 = ['по'] _range_1 = ['между', 'меж\.', 'меж'] _range_2 = ['и'] self._span = re.compile("(%s)\s+(?P.+)\s+(%s)\s+(?P.+)" % @@ -239,136 +114,12 @@ class DateDisplayRU(DateDisplay): """ Russian language date display class. """ - long_months = ( "", "января", "февраля", "марта", "апреля", "мая", - "июня", "июля", "августа", "сентября", "октября", - "ноября", "декабря" ) - - short_months = ( "", "янв", "фев", "мар", "апр", "мая", "июн", - "июл", "авг", "сен", "окт", "ноя", "дек" ) - - calendar = ( - "", - "юлианский", - "еврейский", - "республиканский", - "персидский", - "исламский", - "шведский" - ) - - _mod_str = ( - "", - "перед ", - "после ", - "около ", - "", "", "") - - _qual_str = ("", "оцен ", "вычисл ") + # TODO fix BUG 7064: non-Gregorian calendars wrongly use BCE notation for negative dates + # not refactoring _bce_str into base class because it'll be gone under #7064 _bce_str = "%s до н.э." - formats = ( - "ГГГГ-ММ-ДД (ISO)", "Численный", "Месяц День, Год", - "МЕС ДД, ГГГГ", "День Месяц, Год", "ДД МЕС, ГГГГ" - ) - # this must agree with DateDisplayEn's "formats" definition - # (since no locale-specific _display_gregorian exists, here) - - hebrew = ( "", - "тишрея", - "хешвана", - "кислева", - "тевета", - "шевата", - "адара", - "адара бет", - "нисана", - "ияра", - "сивана", - "таммуза", - "ава", - "элула", - ) - - islamic = ( "", - "мухаррама", - "сафара", - "раби-аль-авваля", - "раби-ассани", - "джумада-аль-уля", - "джумада-аль-ахира", - "раджаба", - "шаабана", - "рамадана", - "шавваля", - "зуль-каада", - "зуль-хиджжа", - ) - - persian = ( "", - "фарвардина", - "урдбихишта", - "хурдада", - "тира", - "мурдада", - "шахривара", - "михра", - "абана", - "азара", - "дая", - "бахмана", - "исфаидармуза", - ) - - french = ( "", - "вандемьера", - "брюмера", - "фримера", - "нивоза", - "плювиоза", - "вантоза", - "жерминаля", - "флореаля", - "прериаля", - "мессидора", - "термидора", - "фрюктидора", - "дополнит." - ) - - def display(self, date): - """ - Return a text string representing the date. - """ - mod = date.get_modifier() - cal = date.get_calendar() - qual = date.get_quality() - start = date.get_start_date() - newyear = date.get_new_year() - - qual_str = self._qual_str[qual] - - if mod == Date.MOD_TEXTONLY: - return date.get_text() - elif start == Date.EMPTY: - return "" - elif mod == Date.MOD_SPAN: - d1 = self.display_cal[cal](start) - d2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%sс %s %s %s%s" % (qual_str, d1, 'по', d2, - scal) - elif mod == Date.MOD_RANGE: - d1 = self.display_cal[cal](start) - d2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%s%s %s %s %s%s" % (qual_str, 'между', d1, 'и', - d2, scal) - else: - text = self.display_cal[date.get_calendar()](start) - scal = self.format_extras(cal, newyear) - return "%s%s%s%s" % (qual_str, self._mod_str[mod], - text, scal) + display = DateDisplay.display_formatted #------------------------------------------------------------------------- # diff --git a/gramps/gen/datehandler/_datedisplay.py b/gramps/gen/datehandler/_datedisplay.py index 71c53cc1d..89ee22f2c 100644 --- a/gramps/gen/datehandler/_datedisplay.py +++ b/gramps/gen/datehandler/_datedisplay.py @@ -41,6 +41,8 @@ log = logging.getLogger(".DateDisplay") #------------------------------------------------------------------------- from ..lib.date import Date from . import _grampslocale +from ..utils.grampslocale import GrampsLocale +from ._datestrings import DateStrings #------------------------------------------------------------------------- # @@ -51,78 +53,63 @@ class DateDisplay(object): """ Base date display class. """ - long_months = ( "", "January", "February", "March", "April", "May", - "June", "July", "August", "September", "October", - "November", "December" ) - - short_months = ( "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ) + _locale = GrampsLocale(lang='en_US', languages='en') _tformat = _grampslocale.tformat - hebrew = ( - "", "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", - "AdarI", "AdarII", "Nisan", "Iyyar", "Sivan", "Tammuz", - "Av", "Elul" - ) - - french = ( - '', - "Vendémiaire", - 'Brumaire', - 'Frimaire', - "Nivôse", - "Pluviôse", - "Ventôse", - 'Germinal', - "Floréal", - 'Prairial', - 'Messidor', - 'Thermidor', - 'Fructidor', - 'Extra', - ) - - persian = ( - "", "Farvardin", "Ordibehesht", "Khordad", "Tir", - "Mordad", "Shahrivar", "Mehr", "Aban", "Azar", - "Dey", "Bahman", "Esfand" - ) - - islamic = ( - "", "Muharram", "Safar", "Rabi`al-Awwal", "Rabi`ath-Thani", - "Jumada l-Ula", "Jumada t-Tania", "Rajab", "Sha`ban", - "Ramadan", "Shawwal", "Dhu l-Qa`da", "Dhu l-Hijja" - ) + _ = _grampslocale.glocale.translation.sgettext + formats = ( + # format 0 - must always be ISO + _("YYYY-MM-DD (ISO)"), - swedish = ( - "", "Januari", "Februari", "Mars", - "April", "Maj", "Juni", - "Juli", "Augusti", "September", - "Oktober", "November", "December" - ) + # format # 1 - must always be locale-preferred numerical format + # such as YY.MM.DD, MM-DD-YY, or whatever your locale prefers. + # This should be the format that is used under the locale by + # strftime() for '%x'. + # You may translate this string as "Numerical", "System preferred", or similar. + _("date format|Numerical"), - formats = ("YYYY-MM-DD (ISO)", ) - # this will be overridden if a locale-specific date displayer exists + # Full month name, day, year + _("Month Day, Year"), - calendar = ( - "", "Julian", "Hebrew", "French Republican", - "Persian", "Islamic", "Swedish" + # Abbreviated month name, day, year + _("MON DAY, YEAR"), + + # Day, full month name, year + _("Day Month Year"), + + # Day, abbreviated month name, year + _("DAY MON YEAR") ) - # this will be overridden if a locale-specific date displayer exists + """ + :Note: + Will be overridden if a locale-specific date displayer exists. + + If your localized ``_display_gregorian`` / ``_display_calendar`` are overridden, + you should override the whole formats list according to your own formats, + and you need not localize the format names here. + this ``formats`` must agree with + :meth:`~_display_calendar`/:meth:`~_display_gregorian` + """ + del _ newyear = ("", "Mar1", "Mar25", "Sep1") - _mod_str = ("", "before ", "after ", "about ", "", "", "") - # this will be overridden if a locale-specific date displayer exists - - _qual_str = ("", "estimated ", "calculated ") - # this will be overridden if a locale-specific date displayer exists - _bce_str = "%s B.C.E." # this will be overridden if a locale-specific date displayer exists def __init__(self, format=None): + self._ds = DateStrings(self._locale) + calendar = list(self._ds.calendar) + calendar[Date.CAL_GREGORIAN] = "" # that string only used in parsing, + # gregorian cal name shouldn't be output! + self.calendar = tuple(calendar) + self.short_months = self._ds.short_months + self.swedish = self.long_months = self._ds.long_months + self.hebrew = self._ds.hebrew + self.french = self._ds.french + self.persian = self._ds.persian + self.islamic = self._ds.islamic self.display_cal = [ self._display_gregorian, self._display_julian, @@ -131,12 +118,119 @@ class DateDisplay(object): self._display_persian, self._display_islamic, self._display_swedish] + self._mod_str = self._ds.modifiers + self._qual_str = self._ds.qualifiers if format is None: self.format = 0 else: self.format = format + self._ = _ = self._locale.translation.sgettext + self.FORMATS_long_month_year = { +# Inflection control due to modifier. +# Protocol: DateDisplayXX passes a key to the dictionary in the +# parameter ``inflect`` to ``_display_calendar``. +# The modifier passed is not necessarily the one printed, it's just +# a representative that induces the same inflection control. +# For example, in Russian "before May", "after May", and "about May" +# all require genitive form for May, whereas no modifier (just "May 1234") +# require nominative, so DateDisplayRU.display will pass "before" +# in all 3 cases, collapsing the 3 modifiers into 1. +# +# Another example in Russian is that "between April 1234 and June 1235" +# requires the same inflection for both April and June, so just "between" +# is used by DateDisplayRU.display, collapsing two more modifiers into 1. +# +# If inflect is not specified, then it means that the modifier doesn't have +# grammatical control over the format, and so the format can be +# localized in a context-free way. +# The translator is responsible for: +# 1) proper collapse of modifier classes +# 2) translating the formats that are selected in runtime +# 3) ignoring the other formats in .po (it does no harm to translate them, +# it's just a lot of extra work) +# +# To prevent POT pollution, not all possibilities are populated here yet. +# To be amended as the actual localized handlers use it. +# +# Not moving to DateStrings, as this is part of display code only, +# coupled tightly with the formats used in this file. + "" + : _("{long_month} {year}"), + + "from" + # first date in a span + # You only need to translate this string if you translate one of the + # inflect=_("...") with "from" + : _("from|{long_month} {year}"), + + "to" + # second date in a span + # You only need to translate this string if you translate one of the + # inflect=_("...") with "to" + : _("to|{long_month} {year}"), + + "between" + # first date in a range + # You only need to translate this string if you translate one of the + # inflect=_("...") with "between" + : _("between|{long_month} {year}"), + + "and" + # second date in a range + # You only need to translate this string if you translate one of the + # inflect=_("...") with "and" + : _("and|{long_month} {year}"), + + "before" + # You only need to translate this string if you translate one of the + # inflect=_("...") with "before" + : _("before|{long_month} {year}"), + + "after" + # You only need to translate this string if you translate one of the + # inflect=_("...") with "after" + : _("after|{long_month} {year}"), + + "about" + # You only need to translate this string if you translate one of the + # inflect=_("...") with "about" + : _("about|{long_month} {year}"), + + # TODO if no modifier, but with qual, might need to inflect in some lang. + } + + self.FORMATS_short_month_year = { + "" + : _("{short_month} {year}"), + + "from" + # first date in a span + : _("from|{short_month} {year}"), + + "to" + # second date in a span + : _("to|{short_month} {year}"), + + "between" + # first date in a range + : _("between|{short_month} {year}"), + + "and" + # second date in a range + : _("and|{short_month} {year}"), + + "before" + : _("before|{short_month} {year}"), + + "after" + : _("after|{short_month} {year}"), + + "about" + : _("about|{short_month} {year}"), + } + def set_format(self, format): self.format = format @@ -164,7 +258,9 @@ class DateDisplay(object): def display(self, date): """ Return a text string representing the date. - (will be overridden if a locale-specific date displayer exists) + + (Will be overridden if a locale-specific date displayer exists.) + Disregard any format settings and use display_iso for each date. """ mod = date.get_modifier() cal = date.get_calendar() @@ -218,10 +314,115 @@ class DateDisplay(object): else: return value - def _display_gregorian(self, date_val): + def display_formatted(self, date): + """ + Return a text string representing the date, according to the format. + """ + mod = date.get_modifier() + cal = date.get_calendar() + qual = date.get_quality() + start = date.get_start_date() + newyear = date.get_new_year() + + qual_str = self._qual_str[qual] + _ = self._ + + if mod == Date.MOD_TEXTONLY: + return date.get_text() + elif start == Date.EMPTY: + return "" + elif mod == Date.MOD_SPAN: + d1 = self.display_cal[cal](start, + # If there is no special inflection for "from " in your + # language, don't translate this string. + # Otherwise, translate it to the ENGLISH!!! ENGLISH!!! + # key appearing above in the FORMATS_... dict + # that maps to the special inflected format string that you need to localize. + inflect=_("from-date|")) + d2 = self.display_cal[cal](date.get_stop_date(), + # If there is no special inflection for "to " in your + # language, don't translate this string. + # Otherwise, translate it to the ENGLISH!!! ENGLISH!!! + # key appearing above in the FORMATS_... dict + # that maps to the special inflected format string that you need to localize. + inflect=_("to-date|")) + scal = self.format_extras(cal, newyear) + return _("{date_quality}from {date_start} to {date_stop}" + "{nonstd_calendar_and_ny}").format( + date_quality=qual_str, + date_start=d1, + date_stop=d2, + nonstd_calendar_and_ny=scal) + elif mod == Date.MOD_RANGE: + d1 = self.display_cal[cal](start, + # If there is no special inflection for "between " in your + # language, don't translate this string. + # Otherwise, translate it to the ENGLISH!!! ENGLISH!!! + # key appearing above in the FORMATS_... dict + # that maps to the special inflected format string that you need to localize. + inflect=_("between-date|")) + d2 = self.display_cal[cal](date.get_stop_date(), + # If there is no special inflection for "and " in your + # language, don't translate this string. + # Otherwise, translate it to the ENGLISH!!! ENGLISH!!! + # key appearing above in the FORMATS_... dict + # that maps to the special inflected format string that you need to localize. + inflect=_("and-date|")) + scal = self.format_extras(cal, newyear) + return _("{date_quality}between {date_start} and {date_stop}" + "{nonstd_calendar_and_ny}").format( + date_quality=qual_str, + date_start=d1, + date_stop=d2, + nonstd_calendar_and_ny=scal) + else: + text = self.display_cal[date.get_calendar()](start, + # If there is no special inflection for "before/after/around " in your + # language, don't translate this string. + # Otherwise, translate it to the ENGLISH!!! ENGLISH!!! + # key appearing above in the FORMATS_... dict + # that maps to the special inflected format string that you need to localize. + # TODO are there languages for which the inflections for the different + # modifiers are different?! + inflect=_("before-date|") if mod != Date.MOD_NONE else "") + scal = self.format_extras(cal, newyear) + return _("{date_quality}{noncompound_modifier}{date}" + "{nonstd_calendar_and_ny}").format( + date_quality=qual_str, + noncompound_modifier=self._mod_str[mod], + date=text, + nonstd_calendar_and_ny=scal) + + def _display_gregorian(self, date_val, **kwargs): + return self._display_calendar(date_val, self.long_months, + self.short_months, **kwargs) + + # Julian and Swedish date display is the same as Gregorian + _display_julian = _display_swedish = _display_gregorian + + def _display_calendar(self, date_val, long_months, short_months = None, + inflect=""): + + if short_months is None: + # Let the short formats work the same as long formats + short_months = long_months + + _ = self._locale.translation.sgettext # this one must agree with DateDisplayEn's "formats" definition # (it may be overridden if a locale-specific date displayer exists) year = self._slash_year(date_val[2], date_val[3]) + + # For partial dates, several formats reduce to just month + year. + def format_long_month_year(): + return self.FORMATS_long_month_year[inflect].format( + long_month = long_months[date_val[1]], + year = year) + + def format_short_month_year(): + return self.FORMATS_short_month_year[inflect].format( + short_month = short_months[date_val[1]], + year = year) + if self.format == 0: return self.display_iso(date_val) elif self.format == 1: @@ -242,30 +443,45 @@ class DateDisplay(object): if date_val[1] == 0: value = year else: - value = "%s %s" % (self.long_months[date_val[1]], year) + value = format_long_month_year() else: - value = "%s %d, %s" % (self.long_months[date_val[1]], - date_val[0], year) + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection for your language. + value = _("{long_month} {day:d}, {year}").format( + long_month = long_months[date_val[1]], + day = date_val[0], + year = year) elif self.format == 3: # month_abbreviation day, year if date_val[0] == 0: if date_val[1] == 0: value = year else: - value = "%s %s" % (self.short_months[date_val[1]], year) + value = format_short_month_year() else: - value = "%s %d, %s" % (self.short_months[date_val[1]], - date_val[0], year) + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection for your language. + value = _("{short_month} {day:d}, {year}").format( + short_month = short_months[date_val[1]], + day = date_val[0], + year = year) elif self.format == 4: # day month_name year if date_val[0] == 0: if date_val[1] == 0: value = year else: - value = "%s %s" % (self.long_months[date_val[1]], year) + value = format_long_month_year() else: - value = "%d %s %s" % (date_val[0], - self.long_months[date_val[1]], year) + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection for your language. + value = _("{day:d} {long_month} {year}").format( + day = date_val[0], + long_month = long_months[date_val[1]], + year = year) # elif self.format == 5: else: # day month_abbreviation year @@ -273,78 +489,39 @@ class DateDisplay(object): if date_val[1] == 0: value = year else: - value = "%s %s" % (self.short_months[date_val[1]], year) + value = format_short_month_year() else: - value = "%d %s %s" % (date_val[0], - self.short_months[date_val[1]], year) + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection for your language. + value = _("{day:d} {short_month} {year}").format( + short_month = short_months[date_val[1]], + day = date_val[0], + year = year) if date_val[2] < 0: + # TODO fix BUG 7064: non-Gregorian calendars wrongly use BCE notation for negative dates return self._bce_str % value else: return value - def _display_julian(self, date_val): - # Julian date display is the same as Gregorian - return self._display_gregorian(date_val) - def _display_calendar(self, date_val, month_list): - # used to display non-Gregorian calendars (Hebrew, Islamic, etc.) - year = abs(date_val[2]) - if self.format == 0 or self.format == 1: - return self.display_iso(date_val) - else: - if date_val[0] == 0: - if date_val[1] == 0: - value = year - else: - value = "%s %d" % (month_list[date_val[1]], year) - else: - value = "%s %d, %s" % (month_list[date_val[1]], date_val[0], - year) - if date_val[2] < 0: - return self._bce_str % value - else: - return value + def _display_french(self, date_val, **kwargs): + return self._display_calendar(date_val, self.french, **kwargs) - def _display_french(self, date_val): - year = abs(date_val[2]) - if self.format == 0 or self.format == 1: - return self.display_iso(date_val) - else: - if date_val[0] == 0: - if date_val[1] == 0: - value = year - else: - value = "%s %d" % (self.french[date_val[1]], year) - else: - value = "%d %s %s" % (date_val[0], self.french[date_val[1]], - year) - if date_val[2] < 0: - return self._bce_str % value - else: - return value + def _display_hebrew(self, date_val, **kwargs): + return self._display_calendar(date_val, self.hebrew, **kwargs) - def _display_hebrew(self, date_val): - return self._display_calendar(date_val, self.hebrew) + def _display_persian(self, date_val, **kwargs): + return self._display_calendar(date_val, self.persian, **kwargs) - def _display_persian(self, date_val): - return self._display_calendar(date_val, self.persian) - - def _display_islamic(self, date_val): - return self._display_calendar(date_val, self.islamic) - - def _display_swedish(self, date_val): - return self._display_calendar(date_val, self.swedish) + def _display_islamic(self, date_val, **kwargs): + return self._display_calendar(date_val, self.islamic, **kwargs) class DateDisplayEn(DateDisplay): """ English language date display class. """ - formats = ( - "YYYY-MM-DD (ISO)", "Numerical", "Month Day, Year", - "MON DAY, YEAR", "Day Month Year", "DAY MON YEAR" - ) - # this (English) "formats" must agree with "_display_gregorian" (above) def __init__(self, format=None): """ @@ -355,33 +532,5 @@ class DateDisplayEn(DateDisplay): DateDisplay.__init__(self, format) - def display(self, date): - """ - Return a text string representing the date. - """ - mod = date.get_modifier() - cal = date.get_calendar() - qual = date.get_quality() - start = date.get_start_date() - newyear = date.get_new_year() + display = DateDisplay.display_formatted - qual_str = self._qual_str[qual] - - if mod == Date.MOD_TEXTONLY: - return date.get_text() - elif start == Date.EMPTY: - return "" - elif mod == Date.MOD_SPAN: - d1 = self.display_cal[cal](start) - d2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%sfrom %s to %s%s" % (qual_str, d1, d2, scal) - elif mod == Date.MOD_RANGE: - d1 = self.display_cal[cal](start) - d2 = self.display_cal[cal](date.get_stop_date()) - scal = self.format_extras(cal, newyear) - return "%sbetween %s and %s%s" % (qual_str, d1, d2, scal) - else: - text = self.display_cal[date.get_calendar()](start) - scal = self.format_extras(cal, newyear) - return "%s%s%s%s" % (qual_str, self._mod_str[mod], text, scal) diff --git a/gramps/gen/datehandler/_datehandler.py b/gramps/gen/datehandler/_datehandler.py index 941e22454..73a761c1e 100644 --- a/gramps/gen/datehandler/_datehandler.py +++ b/gramps/gen/datehandler/_datehandler.py @@ -49,6 +49,7 @@ from ._dateparser import DateParser from ._datedisplay import DateDisplay, DateDisplayEn from ..constfunc import win, cuni from ..const import GRAMPS_LOCALE as glocale +from gramps.gen.utils.grampslocale import GrampsLocale #------------------------------------------------------------------------- # @@ -99,6 +100,9 @@ def register_datehandler(locales,parse_class,display_class): Registers the passed date parser class and date displayer classes with the specified language locales. + Set the parser_class and display_class ._locale attribute + to the corresponding GrampsLocale object. + @param locales: tuple of strings containing language codes. The character encoding is not included, so the language should be in the form of fr_FR, not fr_FR.utf8 @@ -111,3 +115,5 @@ def register_datehandler(locales,parse_class,display_class): for lang_str in locales: LANG_TO_PARSER[lang_str] = parse_class LANG_TO_DISPLAY[lang_str] = display_class + + parse_class._locale = display_class._locale = GrampsLocale(lang=locales[0]) diff --git a/gramps/gen/datehandler/_dateparser.py b/gramps/gen/datehandler/_dateparser.py index f0bdb2c63..d4f38fe95 100644 --- a/gramps/gen/datehandler/_dateparser.py +++ b/gramps/gen/datehandler/_dateparser.py @@ -51,6 +51,8 @@ log = logging.getLogger(".DateParser") #------------------------------------------------------------------------- from ..lib.date import Date, DateError from . import _grampslocale +from ..utils.grampslocale import GrampsLocale +from ._datestrings import DateStrings #------------------------------------------------------------------------- # @@ -120,6 +122,70 @@ def french_valid(date_tuple): valid = False return valid +def _build_prefix_table(month_to_int, month_variants): + """ + Populate a DateParser.month_to_int-like dict + with all the prefixes found in month_variants. + """ + + month_variants = list(month_variants) # drain the generator, if any + month_to_int_new = {} + + # Populate with full names first, w/o prefixes + log.debug("Mapping full names...") + for i in range(0,len(month_variants)): + for month in month_variants[i]: + m = month.lower() + log.debug("Mapping {} -> {}".format(m, i)) + month_to_int_new[m] = i + month_to_int.update(month_to_int_new) + + log.debug("Mapping new prefixes...") + months_sorted = list(month_to_int_new.keys()) + months_sorted.sort(key=len, reverse=True) + for m in months_sorted: + for prefixlen in reversed(range(1,len(m))): + mp = m[:prefixlen] + if mp.strip() != mp: + continue + if mp in month_to_int: + break + else: + i = month_to_int[m] + log.debug("Mapping {} -> {}".format(mp, i)) + month_to_int[mp] = i + +def _generate_variants(months): + """ + Generate all month variants for passing to _build_prefix_table + @param months an iterable ordered collection, + 1st item is empty, the rest 1..N, for a + calendar with N months overall, contain, each, + an iterable of alternative specifications. + Each such specification can be: + 1) a Lexeme, supporting .variants() + to return the list of variants underneath + 2) a literal string + 3) a |-separated string of alternatives + Empty strings are discarded. + @return generator of lists per month with all variants listed once only + the 1st item will be empty + """ + + for month_lexemes_and_alternatives in months: + v = [] + for m in month_lexemes_and_alternatives: + try: + # Lexeme? ask it to compute the variants it knows + mv = list(m.variants()) + except AttributeError: + # plain string, not a lexeme with inflections... + # Maybe a '|'-separated list of alternatives, maybe empty, + # maybe a single string. Suppress empty strings! + mv = (s for s in m.split('|') if s) + v.extend(mv) + yield(list(set(v))) + #------------------------------------------------------------------------- # # DateParser class @@ -131,6 +197,8 @@ class DateParser(object): converted, the text string is assigned. """ + _locale = GrampsLocale(lang='en', languages='en') + _fmt_parse = re.compile(".*%(\S).*%(\S).*%(\S).*") # RFC-2822 only uses capitalized English abbreviated names, no locales. @@ -141,7 +209,12 @@ class DateParser(object): 'Sep' : 9, 'Oct' : 10, 'Nov' : 11, 'Dec' : 12, } - month_to_int = _grampslocale.month_to_int + # seeded with __init_prefix_tables + swedish_to_int = month_to_int = {} + """ + Map Gregorian month names and their prefixes, wherever unambiguous, + to the relevant integer index (1..12). + """ # modifiers before the date # (overridden if a locale-specific date parser exists) @@ -172,13 +245,7 @@ class DateParser(object): } french_to_int = { - 'vendémiaire' : 1, 'brumaire' : 2, - 'frimaire' : 3, 'nivôse': 4, - 'pluviôse' : 5, 'ventôse' : 6, - 'germinal' : 7, 'floréal' : 8, - 'prairial' : 9, 'messidor' : 10, - 'thermidor' : 11, 'fructidor' : 12, - 'extra' : 13, +# the long ones are seeded with __init_prefix_tables #GEDCOM months 'vend' : 1, 'brum' : 2, 'frim' : 3, 'nivo' : 4, @@ -190,6 +257,8 @@ class DateParser(object): } islamic_to_int = { +# some are already seeded with __init_prefix_tables, +# but it is a pain to separate them out from the variants... "muharram" : 1, "muharram ul haram" : 1, "safar" : 2, "rabi`al-awwal" : 3, "rabi'l" : 3, "rabi`ul-akhir" : 4, @@ -207,44 +276,14 @@ class DateParser(object): "dhu hijja" : 12, "thw al-hijjah" : 12, } - persian_to_int = { - "farvardin" : 1, "ordibehesht" : 2, - "khordad" : 3, "tir" : 4, - "mordad" : 5, "shahrivar" : 6, - "mehr" : 7, "aban" : 8, - "azar" : 9, "dey" : 10, - "bahman" : 11, "esfand" : 12, - } - - swedish_to_int = { - "januari" : 1, "februari" : 2, - "mars" : 3, "april" : 4, - "maj" : 5, "juni" : 6, - "juli" : 7, "augusti" : 8, - "september" : 9, "oktober" : 10, - "november" : 11, "december" : 12, - } - + # seeded with __init_prefix_tables + persian_to_int = { } bce = ["B.C.E.", "B.C.E", "BCE", "B.C.", "B.C", "BC" ] # (overridden if a locale-specific date parser exists) + # seeded with __init_prefix_tables calendar_to_int = { - 'gregorian' : Date.CAL_GREGORIAN, - 'g' : Date.CAL_GREGORIAN, - 'julian' : Date.CAL_JULIAN, - 'j' : Date.CAL_JULIAN, - 'hebrew' : Date.CAL_HEBREW, - 'h' : Date.CAL_HEBREW, - 'islamic' : Date.CAL_ISLAMIC, - 'i' : Date.CAL_ISLAMIC, - 'french' : Date.CAL_FRENCH, - 'french republican': Date.CAL_FRENCH, - 'f' : Date.CAL_FRENCH, - 'persian' : Date.CAL_PERSIAN, - 'p' : Date.CAL_PERSIAN, - 'swedish' : Date.CAL_SWEDISH, - 's' : Date.CAL_SWEDISH, } # (probably overridden if a locale-specific date parser exists) @@ -265,6 +304,31 @@ class DateParser(object): } # (overridden if a locale-specific date parser exists) + _langs = set() + def __init_prefix_tables(self): + lang = self._locale.lang + if lang in DateParser._langs: + log.debug("Prefix tables for {} already built".format(lang)) + return + else: + DateParser._langs.add(lang) + ds = DateStrings(self._locale) + log.debug("Begin building parser prefix tables for {}".format(lang)) + _build_prefix_table(DateParser.month_to_int, + _generate_variants( + zip(ds.long_months, ds.short_months, + ds.swedish_SV, ds.alt_long_months))) + _build_prefix_table(DateParser.hebrew_to_int, + _generate_variants(zip(ds.hebrew))) + _build_prefix_table(DateParser.french_to_int, + _generate_variants(zip(ds.french))) + _build_prefix_table(DateParser.islamic_to_int, + _generate_variants(zip(ds.islamic))) + _build_prefix_table(DateParser.persian_to_int, + _generate_variants(zip(ds.persian))) + _build_prefix_table(DateParser.calendar_to_int, + _generate_variants(zip(ds.calendar))) + def __init__(self): self.init_strings() self.parser = { @@ -294,8 +358,8 @@ class DateParser(object): sorted so that longest keys match first. Any '.' characters are quoted. """ - keys.sort(key=lambda x: len(x), reverse=True) - return '(' + '|'.join([key.replace('.', '\.') for key in keys]) + ')' + keys.sort(key=len, reverse=True) + return '(' + '|'.join([re.escape(key) for key in keys]) + ')' def init_strings(self): """ @@ -308,6 +372,8 @@ class DateParser(object): can be coded after DateParser.init_strings(self) call, that way they override stuff from this method. See DateParserRU() as an example. """ + self.__init_prefix_tables() + self._rfc_mon_str = '(' + '|'.join(list(self._rfc_mons_to_int.keys())) + ')' self._rfc_day_str = '(' + '|'.join(self._rfc_days) + ')' diff --git a/gramps/gen/datehandler/_datestrings.py b/gramps/gen/datehandler/_datestrings.py new file mode 100644 index 000000000..656e01c7e --- /dev/null +++ b/gramps/gen/datehandler/_datestrings.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Vassilii Khachaturov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# $Id$ + +""" +Date strings to translate per each language for display and parsing. +""" +from __future__ import print_function, unicode_literals + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +log = logging.getLogger(".DateStrings") + +#------------------------------------------------------------------------- +# +# DateStrings +# +#------------------------------------------------------------------------- +class DateStrings(object): + """ + String tables for :class:`~DateDisplay` and :class:`~DateParser`. + """ + + # This table needs not be localized, it's only for parsing + # Swedish calendar dates using Swedish month names. + # Display of these months uses the regular long_months. + # TODO should we pack these into alt_long_months instead? + swedish_SV = ( + "", "Januari", "Februari", "Mars", + "April", "Maj", "Juni", + "Juli", "Augusti", "September", + "Oktober", "November", "December" + ) + + + def __init__(self, locale): + _ = locale.translation.lexgettext + + self.long_months = ( "", + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection to be used in your localized + # DateDisplayer code! + _("localized lexeme inflections||January"), + _("localized lexeme inflections||February"), + _("localized lexeme inflections||March"), + _("localized lexeme inflections||April"), + _("localized lexeme inflections||May"), + _("localized lexeme inflections||June"), + _("localized lexeme inflections||July"), + _("localized lexeme inflections||August"), + _("localized lexeme inflections||September"), + _("localized lexeme inflections||October"), + _("localized lexeme inflections||November"), + _("localized lexeme inflections||December") ) + + self.short_months = ( "", + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection to be used in your localized + # DateDisplayer code! + _("localized lexeme inflections - short month form||Jan"), + _("localized lexeme inflections - short month form||Feb"), + _("localized lexeme inflections - short month form||Mar"), + _("localized lexeme inflections - short month form||Apr"), + _("localized lexeme inflections - short month form||May"), + _("localized lexeme inflections - short month form||Jun"), + _("localized lexeme inflections - short month form||Jul"), + _("localized lexeme inflections - short month form||Aug"), + _("localized lexeme inflections - short month form||Sep"), + _("localized lexeme inflections - short month form||Oct"), + _("localized lexeme inflections - short month form||Nov"), + _("localized lexeme inflections - short month form||Dec") ) + + _ = locale.translation.sgettext + self.alt_long_months = ( "", + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to add proper alternatives to be recognized in your localized + # DateParser code! + _("alternative month names for January||"), + _("alternative month names for February||"), + _("alternative month names for March||"), + _("alternative month names for April||"), + _("alternative month names for May||"), + _("alternative month names for June||"), + _("alternative month names for July||"), + _("alternative month names for August||"), + _("alternative month names for September||"), + _("alternative month names for October||"), + _("alternative month names for November||"), + _("alternative month names for December||") ) + + self.calendar = ( +# Must appear in the order indexed by Date.CAL_... numeric constants + _("calendar|Gregorian"), + _("calendar|Julian"), + _("calendar|Hebrew"), + _("calendar|French Republican"), + _("calendar|Persian"), + _("calendar|Islamic"), + _("calendar|Swedish") ) + _ = locale.translation.lexgettext + + self.hebrew = ( + "", + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection to be used in your localized + # DateDisplayer code! + _("Hebrew month lexeme|Tishri"), + _("Hebrew month lexeme|Heshvan"), + _("Hebrew month lexeme|Kislev"), + _("Hebrew month lexeme|Tevet"), + _("Hebrew month lexeme|Shevat"), + _("Hebrew month lexeme|AdarI"), + _("Hebrew month lexeme|AdarII"), + _("Hebrew month lexeme|Nisan"), + _("Hebrew month lexeme|Iyyar"), + _("Hebrew month lexeme|Sivan"), + _("Hebrew month lexeme|Tammuz"), + _("Hebrew month lexeme|Av"), + _("Hebrew month lexeme|Elul") + ) + + self.french = ( + "", + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection to be used in your localized + # DateDisplayer code! + _("French month lexeme|Vendémiaire"), + _("French month lexeme|Brumaire"), + _("French month lexeme|Frimaire"), + _("French month lexeme|Nivôse"), + _("French month lexeme|Pluviôse"), + _("French month lexeme|Ventôse"), + _("French month lexeme|Germinal"), + _("French month lexeme|Floréal"), + _("French month lexeme|Prairial"), + _("French month lexeme|Messidor"), + _("French month lexeme|Thermidor"), + _("French month lexeme|Fructidor"), + _("French month lexeme|Extra"), + ) + + self.islamic = ( + "", + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection to be used in your localized + # DateDisplayer code! + _("Islamic month lexeme|Muharram"), + _("Islamic month lexeme|Safar"), + _("Islamic month lexeme|Rabi`al-Awwal"), + _("Islamic month lexeme|Rabi`ath-Thani"), + _("Islamic month lexeme|Jumada l-Ula"), + _("Islamic month lexeme|Jumada t-Tania"), + _("Islamic month lexeme|Rajab"), + _("Islamic month lexeme|Sha`ban"), + _("Islamic month lexeme|Ramadan"), + _("Islamic month lexeme|Shawwal"), + _("Islamic month lexeme|Dhu l-Qa`da"), + _("Islamic month lexeme|Dhu l-Hijja"), + ) + + self.persian = ( + "", + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection to be used in your localized + # DateDisplayer code! + _("Persian month lexeme|Farvardin"), + _("Persian month lexeme|Ordibehesht"), + _("Persian month lexeme|Khordad"), + _("Persian month lexeme|Tir"), + _("Persian month lexeme|Mordad"), + _("Persian month lexeme|Shahrivar"), + _("Persian month lexeme|Mehr"), + _("Persian month lexeme|Aban"), + _("Persian month lexeme|Azar"), + _("Persian month lexeme|Dey"), + _("Persian month lexeme|Bahman"), + _("Persian month lexeme|Esfand"), + ) + + self.modifiers = ("", + _("date modifier|before "), + _("date modifier|after "), + _("date modifier|about "), + "", "", "") + + self.qualifiers = ("", + _("date quality|estimated "), + _("date quality|calculated "), + ) + + + # TODO extend with further strings + # along with the date displayer and parser refactoring + +__doc__ += """ +__main__ +-------- + +Run this code with the appropriate ``LANG`` and ``LC_DATE`` set for your target language, +in order to generate the .po snippets initialized with the strings from your locale +(from the deprecated data provided in _grampslocale). + +E.g., for French: +:: + LANG=fr_FR.utf8 LC_ALL=fr_FR.utf8 GRAMPS_RESOURCES=$PWD python -m gramps.gen.datehandler._datestrings + +Then merge the output into your language's .po file, and further modify the strings as needed. +Then remove the strings from your language's ``DateParserXX`` and ``DateHandlerXX`` classes. +""" + +if __name__ == '__main__': + import sys + from ..utils.grampslocale import GrampsLocale + from gramps.gen.const import GRAMPS_LOCALE as glocale + from ._grampslocale import (_deprecated_long_months as old_long, + _deprecated_short_months as old_short) + from ._datedisplay import DateDisplay + import gettext + lang = glocale.lang + lang_short = lang[:2] + available_langs = glocale.get_available_translations() + if glocale.check_available_translations(lang) is None: + print ("Translation for current language {lang} not available.\n" + "Available translations: {list}.\n" + "Does po/{lang_short}*.po exist in gramps source tree?!\n" + "Please set your LANG / LC_ALL environment to something else...\n".format( + lang=lang, list=available_langs, lang_short=lang_short), + file=sys.stderr) + sys.exit(1) + + print ("# Generating snippets for {}*.po\n" + "# Available languages: {}".format( + lang_short, available_langs)) + glocale = GrampsLocale(languages=(lang)) + dd = glocale.date_displayer + ds = dd._ds + glocale_EN = GrampsLocale(languages=('en')) + ds_EN = DateStrings(glocale_EN) + + filename = __file__ + + try: + localized_months = dd.__class__.long_months + except AttributeError: + localized_months = old_long + + def print_po_snippet(en_loc_old_lists, context): + for m,localized,old in zip(*en_loc_old_lists): + if m == "": + continue + if m == localized: + localized = old + print ('#: {file}:{line}\n' + 'msgid "{context}{en_month}"\n' + 'msgstr "{localized_month}"\n'.format( + context = context, + file = filename, + line = print_po_snippet.line, + en_month = m, + localized_month = localized)) + print_po_snippet.line += 1 + print_po_snippet.line = 10000 + + try: + localized_months = dd.__class__.long_months + except AttributeError: + localized_months = old_long + print_po_snippet((ds_EN.long_months, localized_months, old_long), + "localized lexeme inflections||") + try: + localized_months = dd.__class__.short_months + except AttributeError: + localized_months = old_short + print_po_snippet((ds_EN.short_months, localized_months, old_short), + "localized lexeme inflections - short month form||") + + try: + loc = dd.__class__.hebrew + print_po_snippet((ds_EN.hebrew, loc, loc), + "Hebrew month lexeme|") + except AttributeError: + pass + + try: + loc = dd.__class__.french + print_po_snippet((ds_EN.french, loc, loc), + "French month lexeme|") + except AttributeError: + pass + + try: + loc = dd.__class__.islamic + print_po_snippet((ds_EN.islamic, loc, loc), + "Islamic month lexeme|") + except AttributeError: + pass + + try: + loc = dd.__class__.persian + print_po_snippet((ds_EN.persian, loc, loc), + "Persian month lexeme|") + except AttributeError: + pass + + try: + loc = dd.__class__._mod_str + print_po_snippet((ds_EN.modifiers, loc, loc), + "date modifier|") + except AttributeError: + pass + + try: + loc = dd.__class__._qual_str + print_po_snippet((ds_EN.qualifiers, loc, loc), + "date quality|") + except AttributeError: + pass diff --git a/gramps/gen/datehandler/_grampslocale.py b/gramps/gen/datehandler/_grampslocale.py index 235b80f23..617daf066 100644 --- a/gramps/gen/datehandler/_grampslocale.py +++ b/gramps/gen/datehandler/_grampslocale.py @@ -46,34 +46,8 @@ codeset = glocale.encoding try: - month_to_int = { - to_uni(locale.nl_langinfo(locale.MON_1), codeset).lower() : 1, - to_uni(locale.nl_langinfo(locale.ABMON_1), codeset).lower() : 1, - to_uni(locale.nl_langinfo(locale.MON_2), codeset).lower() : 2, - to_uni(locale.nl_langinfo(locale.ABMON_2), codeset).lower() : 2, - to_uni(locale.nl_langinfo(locale.MON_3), codeset).lower() : 3, - to_uni(locale.nl_langinfo(locale.ABMON_3), codeset).lower() : 3, - to_uni(locale.nl_langinfo(locale.MON_4), codeset).lower() : 4, - to_uni(locale.nl_langinfo(locale.ABMON_4), codeset).lower() : 4, - to_uni(locale.nl_langinfo(locale.MON_5), codeset).lower() : 5, - to_uni(locale.nl_langinfo(locale.ABMON_5), codeset).lower() : 5, - to_uni(locale.nl_langinfo(locale.MON_6), codeset).lower() : 6, - to_uni(locale.nl_langinfo(locale.ABMON_6), codeset).lower() : 6, - to_uni(locale.nl_langinfo(locale.MON_7), codeset).lower() : 7, - to_uni(locale.nl_langinfo(locale.ABMON_7), codeset).lower() : 7, - to_uni(locale.nl_langinfo(locale.MON_8), codeset).lower() : 8, - to_uni(locale.nl_langinfo(locale.ABMON_8), codeset).lower() : 8, - to_uni(locale.nl_langinfo(locale.MON_9), codeset).lower() : 9, - to_uni(locale.nl_langinfo(locale.ABMON_9), codeset).lower() : 9, - to_uni(locale.nl_langinfo(locale.MON_10), codeset).lower() : 10, - to_uni(locale.nl_langinfo(locale.ABMON_10), codeset).lower(): 10, - to_uni(locale.nl_langinfo(locale.MON_11), codeset).lower() : 11, - to_uni(locale.nl_langinfo(locale.ABMON_11), codeset).lower(): 11, - to_uni(locale.nl_langinfo(locale.MON_12), codeset).lower() : 12, - to_uni(locale.nl_langinfo(locale.ABMON_12), codeset).lower(): 12, - } - - long_months = ( + # here only for the upgrade tool, see _datestrings.py __main__ + _deprecated_long_months = ( "", to_uni(locale.nl_langinfo(locale.MON_1), codeset), to_uni(locale.nl_langinfo(locale.MON_2), codeset), @@ -89,7 +63,7 @@ try: to_uni(locale.nl_langinfo(locale.MON_12), codeset), ) - short_months = ( + _deprecated_short_months = ( "", to_uni(locale.nl_langinfo(locale.ABMON_1), codeset), to_uni(locale.nl_langinfo(locale.ABMON_2), codeset), @@ -141,34 +115,7 @@ try: except: import time - month_to_int = { - to_uni(time.strftime('%B',(1,1,1,1,1,1,1,1,1)), codeset).lower() : 1, - to_uni(time.strftime('%b',(1,1,1,1,1,1,1,1,1)), codeset).lower() : 1, - to_uni(time.strftime('%B',(1,2,1,1,1,1,1,1,1)), codeset).lower() : 2, - to_uni(time.strftime('%b',(1,2,1,1,1,1,1,1,1)), codeset).lower() : 2, - to_uni(time.strftime('%B',(1,3,1,1,1,1,1,1,1)), codeset).lower() : 3, - to_uni(time.strftime('%b',(1,3,1,1,1,1,1,1,1)), codeset).lower() : 3, - to_uni(time.strftime('%B',(1,4,1,1,1,1,1,1,1)), codeset).lower() : 4, - to_uni(time.strftime('%b',(1,4,1,1,1,1,1,1,1)), codeset).lower() : 4, - to_uni(time.strftime('%B',(1,5,1,1,1,1,1,1,1)), codeset).lower() : 5, - to_uni(time.strftime('%b',(1,5,1,1,1,1,1,1,1)), codeset).lower() : 5, - to_uni(time.strftime('%B',(1,6,1,1,1,1,1,1,1)), codeset).lower() : 6, - to_uni(time.strftime('%b',(1,6,1,1,1,1,1,1,1)), codeset).lower() : 6, - to_uni(time.strftime('%B',(1,7,1,1,1,1,1,1,1)), codeset).lower() : 7, - to_uni(time.strftime('%b',(1,7,1,1,1,1,1,1,1)), codeset).lower() : 7, - to_uni(time.strftime('%B',(1,8,1,1,1,1,1,1,1)), codeset).lower() : 8, - to_uni(time.strftime('%b',(1,8,1,1,1,1,1,1,1)), codeset).lower() : 8, - to_uni(time.strftime('%B',(1,9,1,1,1,1,1,1,1)), codeset).lower() : 9, - to_uni(time.strftime('%b',(1,9,1,1,1,1,1,1,1)), codeset).lower() : 9, - to_uni(time.strftime('%B',(1,10,1,1,1,1,1,1,1)), codeset).lower() : 10, - to_uni(time.strftime('%b',(1,10,1,1,1,1,1,1,1)), codeset).lower() : 10, - to_uni(time.strftime('%B',(1,11,1,1,1,1,1,1,1)), codeset).lower() : 11, - to_uni(time.strftime('%b',(1,11,1,1,1,1,1,1,1)), codeset).lower() : 11, - to_uni(time.strftime('%B',(1,12,1,1,1,1,1,1,1)), codeset).lower() : 12, - to_uni(time.strftime('%b',(1,12,1,1,1,1,1,1,1)), codeset).lower() : 12, - } - - long_months = ( + _deprecated_long_months = ( "", to_uni(time.strftime('%B',(1,1,1,1,1,1,1,1,1)), codeset), to_uni(time.strftime('%B',(1,2,1,1,1,1,1,1,1)), codeset), @@ -184,7 +131,7 @@ except: to_uni(time.strftime('%B',(1,12,1,1,1,1,1,1,1)), codeset), ) - short_months = ( + _deprecated_short_months = ( "", to_uni(time.strftime('%b',(1,1,1,1,1,1,1,1,1)), codeset), to_uni(time.strftime('%b',(1,2,1,1,1,1,1,1,1)), codeset), diff --git a/gramps/gen/datehandler/test/datedisplay_test.py b/gramps/gen/datehandler/test/datedisplay_test.py new file mode 100644 index 000000000..d4e0bfc73 --- /dev/null +++ b/gramps/gen/datehandler/test/datedisplay_test.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Vassilii Khachaturov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Deeper testing of some DateParser internals. +""" + +from __future__ import print_function, unicode_literals +import unittest + +from ...utils.grampslocale import GrampsLocale +from ...lib.date import Date + +class DateDisplayTest(unittest.TestCase): + def setUp(self): + from .._datedisplay import DateDisplay + self.display = DateDisplay() + self.display_RU = GrampsLocale(lang='ru').date_displayer + + def assert_map_key_val(self, m, k, v): + try: + self.assertEqual(m[k], v) + except KeyError: + self.assertTrue(False, list(m.items())) + +class DateDisplayCalendarTest(DateDisplayTest): + def test_calendar_gregorian_is_empty(self): + self.assert_map_key_val(self.display.calendar, Date.CAL_GREGORIAN, "") + + def test_calendar_julian_RU(self): + self.assert_map_key_val(self.display_RU.calendar, Date.CAL_JULIAN, 'юлианский') + +# This class tests common functionality in DateDisplay as applied to RU, +# and so it is coupled to translated strings and inflection names +# extracted by lexgettext from ru.po +class DateDisplayInflectionsTestRU(DateDisplayTest): + def setUp(self): + DateDisplayTest.setUp(self) + self.dd = self.display = self.display_RU + self.months = self.dd._ds.long_months + # TODO hardwired magic numbers! Bad API smell. + self.dd.set_format(4) # day month_name year + self.may = self.months[5] + + def assertInflectionInDate(self, inflection, date, month=None): + if month is None: + month = date.get_month() + month_lexeme = self.months[month] + self.assertIn(month_lexeme.f[inflection], + self.dd.display(date)) + + def test_month_only_date_nominative(self): + for qual in (Date.QUAL_NONE, Date.QUAL_ESTIMATED, Date.QUAL_CALCULATED): + d1945may = Date(1945, 5, 0) + d1945may.set_quality(qual) + self.assertInflectionInDate('И', d1945may) + + def test_day_month_date_genitive(self): + d1945may9 = Date(1945, 5, 9) + self.assertInflectionInDate('Р', d1945may9) + + def test_before_month_only_date_genitive(self): + d1945may = Date(1945, 5, 0) + d1945may.set_modifier(Date.MOD_BEFORE) + # TODO hardwired magic numbers! Bad API smell. + for inflecting_format in (3,4): + self.dd.set_format(inflecting_format) +# this depends on the fact that in Russian the short and long forms for May +# will be the same! + self.assertIn("до мая", self.dd.display(d1945may)) + + def test_between_month_only_dates_ablative(self): + b1945may_1946may = Date() + b1945may_1946may.set( + modifier=Date.MOD_RANGE, + value=(0, 5, 1945, False, 0, 5, 1946, False)) + # TODO hardwired magic numbers! Bad API smell. + for inflecting_format in (3,4): + self.dd.set_format(inflecting_format) +# this depends on the fact that in Russian the short and long forms for May +# will be the same! + self.assertIn("между маем", self.dd.display(b1945may_1946may)) + self.assertIn("и маем", self.dd.display(b1945may_1946may)) + + def test_month_only_date_span_from_genitive_to_accusative(self): + f1945may_t1946may = Date() + f1945may_t1946may.set( + modifier=Date.MOD_SPAN, + value=(0, 5, 1945, False, 0, 5, 1946, False)) + # TODO hardwired magic numbers! Bad API smell. + for inflecting_format in (3,4): + self.dd.set_format(inflecting_format) +# this depends on the fact that in Russian the short and long forms for May +# will be the same! + self.assertIn("с мая", self.dd.display(f1945may_t1946may)) + self.assertIn("по май", self.dd.display(f1945may_t1946may)) + +if __name__ == "__main__": + unittest.main() diff --git a/gramps/gen/datehandler/test/dateparser_test.py b/gramps/gen/datehandler/test/dateparser_test.py new file mode 100644 index 000000000..806db82ab --- /dev/null +++ b/gramps/gen/datehandler/test/dateparser_test.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Vassilii Khachaturov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Deeper testing of some DateParser internals. +""" + +from __future__ import print_function, unicode_literals +import unittest + +from ...utils.grampslocale import GrampsLocale +from ...lib.date import Date + +class DateParserTest(unittest.TestCase): + def setUp(self): + from .._dateparser import DateParser + self.parser = DateParser() + self.parser_RU = GrampsLocale(lang='ru').date_parser + + def assert_map_key_val(self, m, k, v): + try: + self.assertEqual(m[k], v) + except KeyError: + self.assertTrue(False, list(m.items())) + + def test_month_to_int_jan_is_1(self): + self.assert_map_key_val(self.parser.month_to_int, 'jan', 1) + + def test_prefix_table_for_RU_built(self): + self.assertIn('ru_RU', self.parser._langs) + + def test_month_to_int_septem_RU_is_9(self): + self.assert_map_key_val(self.parser.month_to_int, 'сентяб', 9) + + def test_hebrew_to_int_av_is_12(self): + self.assert_map_key_val(self.parser.hebrew_to_int, 'av', 12) + self.assert_map_key_val(self.parser.hebrew_to_int, 'ав', 12) # RU + + def test_french_to_int_thermidor_is_11(self): + self.assert_map_key_val(self.parser.french_to_int, 'thermidor', 11) + self.assert_map_key_val(self.parser.french_to_int, 'термидор', 11) # RU + + def test_islamic_to_int_ramadan_is_9(self): + self.assert_map_key_val(self.parser.islamic_to_int, 'ramadan', 9) + self.assert_map_key_val(self.parser.islamic_to_int, 'рамадан', 9) # RU + + def test_persian_to_int_tir_is_4(self): + self.assert_map_key_val(self.parser.persian_to_int, 'tir', 4) + self.assert_map_key_val(self.parser.persian_to_int, 'тир', 4) # RU + + def test_calendar_to_int_gregorian(self): + self.assert_map_key_val(self.parser.calendar_to_int, 'gregorian', Date.CAL_GREGORIAN) + self.assert_map_key_val(self.parser.calendar_to_int, 'g', Date.CAL_GREGORIAN) + self.assert_map_key_val(self.parser.calendar_to_int, 'григорианский', Date.CAL_GREGORIAN) + self.assert_map_key_val(self.parser.calendar_to_int, 'г', Date.CAL_GREGORIAN) + + def test_calendar_to_int_julian(self): + self.assert_map_key_val(self.parser.calendar_to_int, 'julian', Date.CAL_JULIAN) + self.assert_map_key_val(self.parser.calendar_to_int, 'j', Date.CAL_JULIAN) + self.assert_map_key_val(self.parser.calendar_to_int, 'юлианский', Date.CAL_JULIAN) + self.assert_map_key_val(self.parser.calendar_to_int, 'ю', Date.CAL_JULIAN) + +class Test_generate_variants(unittest.TestCase): + def setUp(self): + from .. import _datestrings + from .._dateparser import _generate_variants + self.ds = ds = _datestrings.DateStrings(GrampsLocale(languages=('ru'))) + self.month_variants = list(_generate_variants( + zip(ds.long_months, ds.short_months, + ds.swedish_SV, ds.alt_long_months))) + + def testVariantsSameLengthAsLongMonths(self): + self.assertEqual(len(self.ds.long_months), + len(self.month_variants)) + + def testRussianHasDifferentVariantsForEachMonth(self): + for i in range(1, 13): + mvi = self.month_variants[i] + self.assertTrue(len(mvi) > 1, msg=mvi) + + def testNoEmptyStringInVariants(self): + for i in range(1, 13): + mvi = self.month_variants[i] + self.assertNotIn("", mvi) + + def testLongMonthsAppearInVariants(self): + for i in range(1, 13): + lmi = self.ds.long_months[i] + mvi = self.month_variants[i] + self.assertIn("{}".format(lmi), mvi) + + def testShortMonthsAppearInVariants(self): + for i in range(1, 13): + smi = self.ds.short_months[i] + mvi = self.month_variants[i] + self.assertIn("{}".format(smi), mvi) + + def testLongMonthVariantsUnique(self): + for i in range(1, 13): + mvi = self.month_variants[i] + self.assertEqual(len(mvi), len(set(mvi)), msg=mvi) + + def testRuMayVariantsContainSvMaj(self): + v = self.month_variants[5] + self.assertIn("Maj", v) + +if __name__ == "__main__": + unittest.main() diff --git a/gramps/gen/datehandler/test/datestrings_test.py b/gramps/gen/datehandler/test/datestrings_test.py new file mode 100644 index 000000000..6157b26af --- /dev/null +++ b/gramps/gen/datehandler/test/datestrings_test.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Vassilii Khachaturov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +from __future__ import print_function, unicode_literals + +import unittest + +from .. import _datestrings +from ...lib.date import Date + +class DateStringsTest(unittest.TestCase): + def setUp(self): + from ...utils.grampslocale import GrampsLocale + self.ds = _datestrings.DateStrings(GrampsLocale()) # whatever the default... + self.ds_EN = _datestrings.DateStrings(GrampsLocale(languages='en')) + self.ds_RU = _datestrings.DateStrings(GrampsLocale(languages='ru')) + + def testTwelfthMonthIsDecember(self): + self.assertEqual(self.ds_EN.long_months[12], 'December') + self.assertEqual(self.ds_EN.short_months[12], 'Dec') + + # May is 3-letter in Russian, and so abbreviated form + # will be different for inflections! + def testRussianHasDifferentInflectionsForShortMay(self): + v5 = list(self.ds_RU.short_months[5].variants()) + self.assertTrue(len(v5) > 1, msg=v5) + + def testEnAdarI_in_AdarII(self): + adar1 = self.ds_EN.hebrew[6] + adar2 = self.ds_EN.hebrew[7] + self.assertIn(str(adar1), str(adar2)) + + def testEnLastFrenchIsExtra(self): + self.assertEqual(str(self.ds_EN.french[-1]), "Extra") + + def testEnPersianKhordadMordad(self): + khordad = self.ds_EN.persian[3].lower() + mordad = self.ds_EN.persian[5].lower() + self.assertEqual(khordad, "khordad") + self.assertEqual(mordad, "mordad") + + def testEnIslamicRamadan9(self): + self.assertEqual(str(self.ds_EN.islamic[9]), "Ramadan") + + def testFirstStringEmpty(self): + self.assertEqual(self.ds.long_months[0], "") + self.assertEqual(self.ds.short_months[0], "") + self.assertEqual(self.ds.alt_long_months[0], "") + + def testCalendarIndex(self): + self.assertEqual(self.ds_EN.calendar[Date.CAL_GREGORIAN], "Gregorian") + self.assertEqual(self.ds_EN.calendar[Date.CAL_JULIAN], "Julian") + self.assertEqual(self.ds_EN.calendar[Date.CAL_HEBREW], "Hebrew") + self.assertEqual(self.ds_EN.calendar[Date.CAL_FRENCH], "French Republican") + self.assertEqual(self.ds_EN.calendar[Date.CAL_PERSIAN], "Persian") + self.assertEqual(self.ds_EN.calendar[Date.CAL_ISLAMIC], "Islamic") + self.assertEqual(self.ds_EN.calendar[Date.CAL_SWEDISH], "Swedish") + +if __name__ == "__main__": + unittest.main() diff --git a/gramps/gen/lib/date.py b/gramps/gen/lib/date.py index b586c021c..a7b4bf919 100644 --- a/gramps/gen/lib/date.py +++ b/gramps/gen/lib/date.py @@ -530,7 +530,7 @@ class Date(object): QUAL_NONE = 0 # BITWISE QUAL_ESTIMATED = 1 QUAL_CALCULATED = 2 - QUAL_INTERPRETED = 4 + #QUAL_INTERPRETED = 4 unused in source!! CAL_GREGORIAN = 0 # CODE CAL_JULIAN = 1 diff --git a/gramps/gen/utils/grampslocale.py b/gramps/gen/utils/grampslocale.py index cd19bad5b..439d2a3ef 100644 --- a/gramps/gen/utils/grampslocale.py +++ b/gramps/gen/utils/grampslocale.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # # Gramps - a GTK+/GNOME based genealogy program # @@ -26,12 +27,13 @@ # python modules # #------------------------------------------------------------------------ -from __future__ import print_function +from __future__ import print_function, unicode_literals import gettext import sys import os import codecs import locale +import collections import logging LOG = logging.getLogger("." + __name__) @@ -155,6 +157,7 @@ class GrampsLocale(object): codes corresponding to subidrectries in the localedir, e.g.: "fr" or "zh_CN". """ + DEFAULT_TRANSLATION_STR = "default" __first_instance = None encoding = None @@ -936,6 +939,128 @@ class GrampsLocale(object): # Translations Classes # #------------------------------------------------------------------------- +if sys.version_info < (3,0): + _LexemeBaseStr = unicode + _isstring = lambda s: isinstance(s, basestring) +else: + _LexemeBaseStr = str + _isstring = lambda s: isinstance(s, str) +class Lexeme(_LexemeBaseStr): + r""" + Created with :meth:`~GrampsTranslations.lexgettext` + + .. rubric:: Example + Python code: + :: + _ = lexgettext + dec = _("localized lexeme inflections||December") + xmas = _("lexeme||Christmas") + text = _("{holiday} is celebrated in {month}".format( + holiday=xmas, month=dec)) + greeting = _("Merry {holiday}!").format(holiday=xmas) + XMAS = xmas.upper() + print ("\n".join([XMAS, text, greeting])) + + Translation database (Russian example): + :: + msgid "lexeme||December" + msgstr "NOMINATIVE=декабрь|GENITIVE=декабря|ABLATIVE=декабрём|LOCATIVE=декабре" + + msgid "lexeme||Christmas" + msgstr "NOMINATIVE=рождество|GENITIVE=рождества|ABLATIVE=рождеством" + + msgid "{holiday} is celebrated in {month}" + msgstr "{holiday} празднуют в {month.f[LOCATIVE]}" + + msgid "Merry {holiday}!" + msgstr "Счастливого {holiday.f[GENITIVE]}!" + + Prints out: + In English locale: + :: + CHRISTMAS + Christmas is celebrated in December + Merry Christmas! + In Russian locale: + :: + РОЖДЕСТВО + рождество празднуют в декабре + Счастливого рождества! + + .. rubric:: Description + Stores an arbitrary number of forms, e.g., inflections. + These forms are accessible under dictionary keys for each form. + The names of the forms are language-specific. They are assigned + by the human translator of the corresponding language (in XX.po) + as in the example above, + see :meth:`~GrampsTranslations.lexgettext` docs + for more info. + + The translated format string can then refer to a specific form + of the lexeme using ``.``:attr:`~Lexeme.f` and square brackets: + ``{holiday.f[GENITIVE]}`` + expects holiday to be a Lexeme which has a form ``'GENITIVE'`` in it. + + An instance of Lexeme can also be used as a regular unicode string. + In this case, the work will be delegated to the string for the very + first form provided in the translated string. In the example above, + ``{holiday}`` in the translated string will expand to the Russian + nominative form for Christmas, and ``xmas.upper()`` will produce + the same nominative form in capital letters. + + .. rubric:: Motivation + Lexeme is the term used in linguistics for the set of forms taken + by a particular word, e.g. cases for a noun or tenses for a verb. + + Gramps often needs to compose sentences from several blocks of + text and single words, often by using python string formatting. + + For instance, formatting a date range is done similarly to this: + :: + _("Between {startdate_month} {startdate_year}" + "and {enddate_month} {enddate_year}").format( + startdate_month = m1, + startdate_year = y1, + enddate_month = m2, + enddate_year = y2) + + To make such text translatable, the arguments injected into + format string need to bear all the linguistical information + on how to plug them into a sentence, i.e., the forms, depending + on the linguistic context of where the argument appears. + The format string needs to select the relevant linguistic form. + This is why ``m1`` and ``m2`` are instances of :class:`~Lexeme`. + + On the other hand, for languages where there is no linguistic + variation in such sentences, the code needs not to be aware of + the underlying :class:`~Lexeme` complexity; + and so they can be processed just like simple strings + both when passed around in the code and when formatted. + """ + + def __new__(cls, iterable, *args, **kwargs): + if _isstring(iterable): + newobj = _LexemeBaseStr.__new__(cls, iterable, *args, **kwargs) + else: + od = collections.OrderedDict(iterable) + l = list(od.values()) or [""] + newobj = _LexemeBaseStr.__new__(cls, l[0], *args, **kwargs) + newobj._forms = od + return newobj + + def variants(self): + """All lexeme forms, in the same order as given upon construction. + The first one returned is the default form, which is used when the + Lexeme instance is used in lieu of a string object. + + Same as ``f.values()``""" + return self._forms.values() + + @property + def f(self): + """Dictionary of the lexeme forms""" + return self._forms + class GrampsTranslations(gettext.GNUTranslations): """ Overrides and extends gettext.GNUTranslations. See the Python gettext @@ -950,6 +1075,7 @@ class GrampsTranslations(gettext.GNUTranslations): def gettext(self, msgid): """ Obtain translation of gettext, return a unicode object + :param msgid: The string to translated. :type msgid: unicode :returns: Translation or the original. @@ -1009,6 +1135,32 @@ class GrampsTranslations(gettext.GNUTranslations): msgval = msgid[sep_idx+1:] return msgval + def lexgettext(self, msgid): + """ + Extract all inflections of the same lexeme, + stripping the '|'-separated context using :meth:`~sgettext` + + The *resulting* message provided by the translator + is supposed to be '|'-separated as well. + The possible formats are either (1) a single string + for a language with no inflections, or (2) a list of + =, separated with '|'. + For example: + + (1) "Uninflectable" + (2) "n=Inflected-nominative|g=Inflected-genitive|d=Inflected-dative" + + See :class:`~Lexeme` documentation for detailed explanation and example. + + :param msgid: The string to translated. + :type msgid: unicode + :returns: Translation or the original with context stripped. + :rtype: unicode (for option (1)) / Lexeme (option (2)) + """ + variants = self.sgettext(msgid).split('|') + return Lexeme([v.split('=') for v in variants] + ) if len(variants) > 1 else variants[0] + class GrampsNullTranslations(gettext.NullTranslations): """ Extends gettext.NullTranslations to provide the sgettext method. @@ -1023,6 +1175,8 @@ class GrampsNullTranslations(gettext.NullTranslations): msgval = msgid[sep_idx+1:] return msgval + lexgettext = sgettext + def language(self): """ The null translation returns the raw msgids, which are in English diff --git a/gramps/gen/utils/test/grampslocale_test.py b/gramps/gen/utils/test/grampslocale_test.py new file mode 100644 index 000000000..0a3ea9b9d --- /dev/null +++ b/gramps/gen/utils/test/grampslocale_test.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Vassilii Khachaturov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +from __future__ import print_function, unicode_literals + +import unittest +import sys + +try: + if sys.version_info < (3,3): + from mock import Mock + else: + from unittest.mock import Mock + + MOCKING = True + +except: + MOCKING = False + print ("Mocking disabled, some testing skipped", sys.exc_info()[0:2]) + +class LexGettextTest(unittest.TestCase): + SRC_WORD = "Inflect-me" + MSGID = "how-to-use-lexgettext||" + SRC_WORD + + def setUp(self): + from ..grampslocale import GrampsTranslations + from ..grampslocale import GrampsLocale as Loc + self.trans = GrampsTranslations() + + def setup_sgettext_mock(self, msgval_expected): + if MOCKING: + mock = Mock(return_value=msgval_expected) + else: + mock = lambda msgid: msgval_expected + self.trans.sgettext = mock + + def tearDown(self): + if MOCKING: + try: + self.trans.sgettext.assert_called_once_with( + self.MSGID) + except AttributeError as e: + print ("Apparently the test has never set up the mock: ", e) + + def testSrcWordOnlyIfNoTranslation(self): + self.setup_sgettext_mock(self.SRC_WORD) + result = self.trans.lexgettext(self.MSGID) + self.assertEqual(result, self.SRC_WORD) + + def test3InflectionsExtractableByNameThroughForm(self): + translated = "n=TargetNom|g=TargetGen|d=TargetDat" + self.setup_sgettext_mock(translated) + lex = self.trans.lexgettext(self.MSGID) + formatted = "{lex.f[n]},{lex.f[g]},{lex.f[d]}".format(lex=lex) + self.assertEqual(formatted, "TargetNom,TargetGen,TargetDat") + + def testFirstLexemeFormExtractableAsDefaultString(self): + translated = "def=Default|v1=Option1|a=AnotherOption" + self.setup_sgettext_mock(translated) + lex = self.trans.lexgettext(self.MSGID) + formatted = "{}".format(lex) + self.assertEqual(formatted, "Default") + +class LexemeTest(unittest.TestCase): + def setUp(self): + from ..grampslocale import Lexeme + self.lex = Lexeme((('a', 'aaa'), ('b', 'bbb'), ('c', 'ccc'))) + self.zlex = Lexeme({'z' : 'zzz'}) + self.elex = Lexeme({}) + + def testIsHashable(self): + hash(self.lex) # throws if not hashable + + # test delegation to an arbitrary string method pulled in from unicode + def testDefaultStringStartsWithAA(self): + self.assertTrue(self.lex.startswith('aa'), + msg="default string: {} dict: {}".format( + self.lex, self.lex.__dict__)) + + def testCanConcatenateStringAndLexeme(self): + moo = "moo" + self.assertEqual(moo + self.lex, "mooaaa") + + def testCanConcatenateStringAndLexemeInPlace(self): + moo = "moo" + moo += self.lex + self.assertEqual(moo, "mooaaa") + + def testCanConcatenateLexemeAndStringInPlace(self): + moo = "moo" + self.lex += moo + self.assertEqual(self.lex, "aaamoo") + + def testCanConcatenateTwoLexemes(self): + aaazzz = self.lex + self.zlex + self.assertEqual(aaazzz, "aaazzz") + + def testCanJoinTwoLexemes(self): + aaa_zzz = "_".join([self.lex,self.zlex]) + self.assertEqual(aaa_zzz, "aaa_zzz") + + def testEmptyIterableLikeEmptyString(self): + self.assertEqual(self.elex, "") + +if __name__ == "__main__": + unittest.main() diff --git a/gramps/plugins/drawreport/calendarreport.py b/gramps/plugins/drawreport/calendarreport.py index fe774e867..70b6ba203 100644 --- a/gramps/plugins/drawreport/calendarreport.py +++ b/gramps/plugins/drawreport/calendarreport.py @@ -194,7 +194,13 @@ class Calendar(Report): self.doc.draw_box("CAL-Title", "", 0, 0, width, header, mark) self.doc.draw_line("CAL-Border", 0, header, width, header) year = self.year - title = "%s %d" % (_dd.long_months[month].capitalize(), year) + # TRANSLATORS: see + # http://gramps-project.org/wiki/index.php?title=Translating_Gramps#Translating_dates + # to learn how to select proper inflection for your language. + title = _("{long_month} {year}").format( + long_month = _dd.long_months[month], + year = year + ).capitalize() mark = IndexMark(title, INDEX_TYPE_TOC, 2) font_height = pt2cm(ptitle.get_font().get_size()) self.doc.center_text("CAL-Title", title, diff --git a/gramps/plugins/drawreport/timeline.py b/gramps/plugins/drawreport/timeline.py index 2bde2d035..c409cdd21 100644 --- a/gramps/plugins/drawreport/timeline.py +++ b/gramps/plugins/drawreport/timeline.py @@ -41,7 +41,7 @@ import copy # #------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale -_ = glocale.translation.gettext +_ = glocale.translation.sgettext from gramps.gen.plug.menu import (PersonOption, FilterOption, EnumeratedListOption) from gramps.gen.plug.report import Report @@ -71,8 +71,8 @@ cal = config.get('preferences.calendar-format-report') #------------------------------------------------------------------------ def _get_sort_functions(sort): return [ - (_("Birth Date"),sort.by_birthdate_key), - (_("Name"),sort.by_last_name_key), + (_("sorted by|Birth Date"),sort.by_birthdate_key), + (_("sorted by|Name"),sort.by_last_name_key), ] #------------------------------------------------------------------------ diff --git a/gramps/plugins/webreport/webcal.py b/gramps/plugins/webreport/webcal.py index b53b1c1d7..9ec4dbf91 100644 --- a/gramps/plugins/webreport/webcal.py +++ b/gramps/plugins/webreport/webcal.py @@ -1703,7 +1703,10 @@ def get_day_list(event_date, holiday_list, bday_anniv_list): if event == 'Birthday': txt_str = (text + ', ' - + (_('%s old') % str(age_str) if nyears else _('birth')) + # TRANSLATORS: expands to smth like "12 years old", + # where "12 years" is already localized to your language + + (_('%s old') % str(age_str) + if nyears else _('birth')) + '') # an anniversary