-
-
Notifications
You must be signed in to change notification settings - Fork 186
/
i18nl10n.py
291 lines (239 loc) · 11 KB
/
i18nl10n.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# -*- coding: utf-8 -*-
"""
Collection of i18n and l10n utility methods.
"""
import re
import logging
from zope.component import getUtility
from zope.i18n import translate
from zope.i18n.locales import locales
from zope.publisher.interfaces.browser import IBrowserRequest
from Acquisition import aq_acquire
from DateTime import DateTime
from DateTime.interfaces import IDateTime
from plone.registry.interfaces import IRegistry
from Products.CMFPlone.utils import log
# these are taken from PTS, used for format interpolation
NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
_interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' % ({'n': NAME_RE}))
datetime_formatvariables = ('H', 'I', 'm', 'd', 'M', 'p', 'S', 'Y', 'y', 'Z')
name_formatvariables = ('a', 'A', 'b', 'B')
ENGLISH_NAMES = {}
try:
from DateTime.DateTime import _DAYS
ENGLISH_NAMES['_days'] = _DAYS
from DateTime.DateTime import _DAYS_A
ENGLISH_NAMES['_days_a'] = _DAYS_A
from DateTime.DateTime import _DAYS_P
ENGLISH_NAMES['_days_p'] = _DAYS_P
from DateTime.DateTime import _MONTHS
ENGLISH_NAMES['_months'] = _MONTHS
from DateTime.DateTime import _MONTHS_A
ENGLISH_NAMES['_months_a'] = _MONTHS_A
from DateTime.DateTime import _MONTHS_P
ENGLISH_NAMES['_months_p'] = _MONTHS_P
except ImportError:
ENGLISH_NAMES['_days'] = DateTime._days
ENGLISH_NAMES['_days_a'] = DateTime._days_a
ENGLISH_NAMES['_days_p'] = DateTime._days_p
ENGLISH_NAMES['_months'] = DateTime._months
ENGLISH_NAMES['_months_a'] = DateTime._months_a
ENGLISH_NAMES['_months_p'] = DateTime._months_p
# The following are helper methods to change the default date and time formats
# for a specific locale. These locale dependent formats are used in the
# date/time widgets to determine the format and decide if a 24 hour or 12 hour
# AM/PM widget should be used. If 'a' is part of the pattern the AM/PM widget
# will be used, otherwise a 24 hour clock.
#
# localeid is a tuple of the form: (language, country, variant)
# for example: (None, ) or ('en', ) or ('en', 'US', None)
#
# value is in the format described by zope.i18n.interfaces.IDateTimeFormat
# for example u'yyyy-MM-dd' or u'HH:mm:ss'
#
# Note that this is a different format than used for the other methods in
# this module.
#
# locales uses a module level cache, so any changes you make with these
# methods will apply to the entire process and only need to be made once.
# You can use them in any code imported at startup, for example in a packages
# __init__ method.
#
# In order to use a 24 hour clock for English speakers, you would do:
#
# from Products.CMFPlone import i18nl10n
# i18nl10n.setDefaultTimeFormat(('en',), u'HH:mm:ss')
def setDefaultDateFormat(localeid, value):
gregorian = locales.getLocale(*localeid).dates.calendars[u'gregorian']
date_format = gregorian.dateFormats['medium'].formats[None]
date_format.pattern = value
def setDefaultTimeFormat(localeid, value):
gregorian = locales.getLocale(*localeid).dates.calendars[u'gregorian']
time_format = gregorian.timeFormats['medium'].formats[None]
time_format.pattern = value
def utranslate(domain, msgid, mapping=None, context=None,
target_language=None, default=None):
# We used to pass an object as context.
if not IBrowserRequest.providedBy(context):
context = aq_acquire(context, 'REQUEST')
# The signature of zope.i18n's translate has the msgid and domain switched
return translate(msgid, domain=domain, mapping=mapping, context=context,
target_language=target_language, default=default)
def get_formatstring_from_registry(msgid):
"""If the Enabled record is True, return a format string."""
registry = getUtility(IRegistry)
name_root = 'Products.CMFPlone.i18nl10n.override_dateformat.'
if registry.get(name_root + 'Enabled', False) is False:
return None
# msgid: "date_format_long", "date_format_short", or "time_format"
record_name = name_root + msgid
return registry.get(record_name, None)
def ulocalized_time(time, long_format=None, time_only=False, context=None,
domain='plonelocales', request=None, target_language=None):
"""unicode aware localized time method (l10n)"""
if time_only:
msgid = 'time_format'
elif long_format:
msgid = 'date_format_long'
else:
msgid = 'date_format_short'
# NOTE: this requires the presence of three msgids inside the translation
# catalog date_format_long, date_format_short, and time_format
# These msgids are translated using interpolation.
# The variables used here are the same as used in the strftime
# formating.
# Supported are:
# %A, %a, %B, %b, %H, %I, %m, %d, %M, %p, %S, %Y, %y, %Z
# Each used as variable in the msgstr without the %.
# For example: "${A} ${d}. ${B} ${Y}, ${H}:${M} ${Z}"
# Each language dependend part is translated itself as well.
# From http://docs.python.org/lib/module-time.html
#
# %a Locale's abbreviated weekday name.
# %A Locale's full weekday name.
# %b Locale's abbreviated month name.
# %B Locale's full month name.
# %d Day of the month as a decimal number [01,31].
# %H Hour (24-hour clock) as a decimal number [00,23].
# %I Hour (12-hour clock) as a decimal number [01,12].
# %m Month as a decimal number [01,12].
# %M Minute as a decimal number [00,59].
# %p Locale's equivalent of either AM or PM.
# %S Second as a decimal number [00,61].
# %y Year without century as a decimal number [00,99].
# %Y Year with century as a decimal number.
# %Z Time zone name (no characters if no time zone exists).
mapping = {}
# convert to DateTime instances. Either a date string or
# a DateTime instance needs to be passed.
if not IDateTime.providedBy(time):
try:
time = DateTime(time)
except:
log('Failed to convert %s to a DateTime object' % time,
severity=logging.DEBUG)
return None
if context is None:
# when without context, we cannot do very much.
return time.ISO8601()
if request is None:
request = aq_acquire(context, 'REQUEST')
# 1. if our Enabled flag in the configuration registry is set,
# the format string there should override the translation machinery
formatstring = get_formatstring_from_registry(msgid)
if formatstring is not None:
return time.strftime(formatstring)
# 2. the normal case: translation machinery,
# that is the ".../LC_MESSAGES/plonelocales.po" files
formatstring = translate(msgid, domain, mapping, request,
target_language=target_language)
# 3. if both failed, fall back to hardcoded ISO style
if formatstring == msgid:
if msgid == 'date_format_long':
formatstring = '%Y-%m-%d %H:%M' # 2038-01-19 03:14
elif msgid == 'date_format_short':
formatstring = '%Y-%m-%d' # 2038-01-19
elif msgid == 'time_format':
formatstring = '%H:%M' # 03:14
else:
formatstring = '[INTERNAL ERROR]'
return time.strftime(formatstring)
# get the format elements used in the formatstring
formatelements = _interp_regex.findall(formatstring)
# reformat the ${foo} to foo
formatelements = [el[2:-1] for el in formatelements]
# add used elements to mapping
elements = [e for e in formatelements if e in datetime_formatvariables]
# add weekday name, abbr. weekday name, month name, abbr month name
week_included = True
month_included = True
name_elements = [e for e in formatelements if e in name_formatvariables]
if not ('a' in name_elements or 'A' in name_elements):
week_included = False
if not ('b' in name_elements or 'B' in name_elements):
month_included = False
for key in elements:
mapping[key] = time.strftime('%' + key)
if week_included:
weekday = int(time.strftime('%w')) # weekday, sunday = 0
if 'a' in name_elements:
mapping['a'] = weekdayname_msgid_abbr(weekday)
if 'A' in name_elements:
mapping['A'] = weekdayname_msgid(weekday)
if month_included:
monthday = int(time.strftime('%m')) # month, january = 1
if 'b' in name_elements:
mapping['b'] = monthname_msgid_abbr(monthday)
if 'B' in name_elements:
mapping['B'] = monthname_msgid(monthday)
# translate translateable elements
for key in name_elements:
mapping[key] = translate(mapping[key], domain,
context=request, default=mapping[key],
target_language=target_language)
# translate the time string
return translate(msgid, domain, mapping, request,
target_language=target_language)
def _numbertoenglishname(number, format=None, attr='_days'):
# returns the english name of day or month number
# starting with Sunday == 0
# and January = 1
# format is either None, 'a' or 'p')
# None means full name (January, February, ...)
# 'a' means abbreviated (Jan, Feb, ..)
# 'p' means abbreviated with . (dot) at end (Jan., Feb., ...)
number = int(number)
if format is not None:
attr = '%s_%s' % (attr, format)
return ENGLISH_NAMES[attr][number]
def monthname_english(number, format=None):
# returns the english name of month with number
return _numbertoenglishname(number, format=format, attr='_months')
def weekdayname_english(number, format=None):
# returns the english name of week with number
return _numbertoenglishname(number, format=format, attr='_days')
def monthname_msgid(number):
# returns the msgid for monthname
# use to translate to full monthname (January, February, ...)
# e.g. month_jan, month_feb, ...
return "month_%s" % monthname_english(number, format='a').lower()
def monthname_msgid_abbr(number):
# returns the msgid for the abbreviated monthname
# use to translate to abbreviated format (Jan, Feb, ...)
# e.g. month_jan_abbr, month_feb_abbr, ...
return "month_%s_abbr" % monthname_english(number, format='a').lower()
def weekdayname_msgid(number):
# returns the msgid for the weekdayname
# use to translate to full weekdayname (Monday, Tuesday, ...)
# e.g. weekday_mon, weekday_tue, ...
return "weekday_%s" % weekdayname_english(number, format='a').lower()
def weekdayname_msgid_abbr(number):
# returns the msgid for abbreviated weekdayname
# use to translate to abbreviated format (Mon, Tue, ...)
# e.g. weekday_mon_abbr, weekday_tue_abbr, ...
return "weekday_%s_abbr" % weekdayname_english(number, format='a').lower()
def weekdayname_msgid_short(number):
# return the msgid for short weekdayname
# use to translate to 2 char format (Mo, Tu, ...)
# e.g. weekday_mon_short, weekday_tue_short, ...
return "weekday_%s_short" % weekdayname_english(number, format='a').lower()