i18n.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. ;;;; i18n.scm --- internationalization support -*- coding: utf-8 -*-
  2. ;;;; Copyright (C) 2006, 2007, 2009, 2010, 2012 Free Software Foundation, Inc.
  3. ;;;;
  4. ;;;; This library is free software; you can redistribute it and/or
  5. ;;;; modify it under the terms of the GNU Lesser General Public
  6. ;;;; License as published by the Free Software Foundation; either
  7. ;;;; version 3 of the License, or (at your option) any later version.
  8. ;;;;
  9. ;;;; This library is distributed in the hope that it will be useful,
  10. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;;;; Lesser General Public License for more details.
  13. ;;;;
  14. ;;;; You should have received a copy of the GNU Lesser General Public
  15. ;;;; License along with this library; if not, write to the Free Software
  16. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. ;;; Author: Ludovic Courtès <ludo@gnu.org>
  18. ;;; Commentary:
  19. ;;;
  20. ;;; This module provides a number of routines that support
  21. ;;; internationalization (e.g., locale-dependent text collation, character
  22. ;;; mapping, etc.). It also defines `locale' objects, representing locale
  23. ;;; settings, that may be passed around to most of these procedures.
  24. ;;;
  25. ;;; Code:
  26. (define-module (ice-9 i18n)
  27. :use-module (ice-9 optargs)
  28. :export (;; `locale' type
  29. make-locale locale?
  30. %global-locale
  31. ;; text collation
  32. string-locale<? string-locale>?
  33. string-locale-ci<? string-locale-ci>? string-locale-ci=?
  34. char-locale<? char-locale>?
  35. char-locale-ci<? char-locale-ci>? char-locale-ci=?
  36. ;; character mapping
  37. char-locale-downcase char-locale-upcase char-locale-titlecase
  38. string-locale-downcase string-locale-upcase string-locale-titlecase
  39. ;; reading numbers
  40. locale-string->integer locale-string->inexact
  41. ;; charset/encoding
  42. locale-encoding
  43. ;; days and months
  44. locale-day-short locale-day locale-month-short locale-month
  45. ;; date and time
  46. locale-am-string locale-pm-string
  47. locale-date+time-format locale-date-format locale-time-format
  48. locale-time+am/pm-format
  49. locale-era locale-era-year
  50. locale-era-date-format locale-era-date+time-format
  51. locale-era-time-format
  52. ;; monetary
  53. locale-currency-symbol
  54. locale-monetary-decimal-point locale-monetary-thousands-separator
  55. locale-monetary-grouping locale-monetary-fractional-digits
  56. locale-currency-symbol-precedes-positive?
  57. locale-currency-symbol-precedes-negative?
  58. locale-positive-separated-by-space?
  59. locale-negative-separated-by-space?
  60. locale-monetary-positive-sign locale-monetary-negative-sign
  61. locale-positive-sign-position locale-negative-sign-position
  62. monetary-amount->locale-string
  63. ;; number formatting
  64. locale-digit-grouping locale-decimal-point
  65. locale-thousands-separator
  66. number->locale-string
  67. ;; miscellaneous
  68. locale-yes-regexp locale-no-regexp))
  69. (eval-when (eval load compile)
  70. (load-extension (string-append "libguile-" (effective-version))
  71. "scm_init_i18n"))
  72. ;;;
  73. ;;; Charset/encoding.
  74. ;;;
  75. (define (locale-encoding . locale)
  76. (apply nl-langinfo CODESET locale))
  77. ;;;
  78. ;;; Months and days.
  79. ;;;
  80. ;; Helper macro: Define a procedure named NAME that maps its argument to
  81. ;; NL-ITEMS. Gnulib guarantees that these items are available.
  82. (define-macro (define-vector-langinfo-mapping name nl-items)
  83. (let* ((item-count (length nl-items))
  84. (defines `(define %nl-items (vector #f ,@nl-items)))
  85. (make-body (lambda (result)
  86. `(if (and (integer? item) (exact? item))
  87. (if (and (>= item 1) (<= item ,item-count))
  88. ,result
  89. (throw 'out-of-range "out of range" item))
  90. (throw 'wrong-type-arg "wrong argument type" item)))))
  91. `(define (,name item . locale)
  92. ,defines
  93. ,(make-body '(apply nl-langinfo (vector-ref %nl-items item) locale)))))
  94. (define-vector-langinfo-mapping locale-day-short
  95. (ABDAY_1 ABDAY_2 ABDAY_3 ABDAY_4 ABDAY_5 ABDAY_6 ABDAY_7))
  96. (define-vector-langinfo-mapping locale-day
  97. (DAY_1 DAY_2 DAY_3 DAY_4 DAY_5 DAY_6 DAY_7))
  98. (define-vector-langinfo-mapping locale-month-short
  99. (ABMON_1 ABMON_2 ABMON_3 ABMON_4 ABMON_5 ABMON_6
  100. ABMON_7 ABMON_8 ABMON_9 ABMON_10 ABMON_11 ABMON_12))
  101. (define-vector-langinfo-mapping locale-month
  102. (MON_1 MON_2 MON_3 MON_4 MON_5 MON_6 MON_7 MON_8 MON_9 MON_10 MON_11 MON_12))
  103. ;;;
  104. ;;; Date and time.
  105. ;;;
  106. ;; Define a procedure NAME that gets langinfo item ITEM. Gnulib's
  107. ;; `nl_langinfo' does not guarantee that all these items are supported
  108. ;; (for instance, `GROUPING' is lacking on Darwin and Gnulib provides no
  109. ;; replacement), so use DEFAULT as the default value when ITEM is not
  110. ;; available.
  111. (define-macro (define-simple-langinfo-mapping name item default)
  112. (let ((body (if (defined? item)
  113. `(apply nl-langinfo ,item locale)
  114. default)))
  115. `(define (,name . locale)
  116. ,body)))
  117. (define-simple-langinfo-mapping locale-am-string
  118. AM_STR "AM")
  119. (define-simple-langinfo-mapping locale-pm-string
  120. PM_STR "PM")
  121. (define-simple-langinfo-mapping locale-date+time-format
  122. D_T_FMT "%a %b %e %H:%M:%S %Y")
  123. (define-simple-langinfo-mapping locale-date-format
  124. D_FMT "%m/%d/%y")
  125. (define-simple-langinfo-mapping locale-time-format
  126. T_FMT "%H:%M:%S")
  127. (define-simple-langinfo-mapping locale-time+am/pm-format
  128. T_FMT_AMPM "%I:%M:%S %p")
  129. (define-simple-langinfo-mapping locale-era
  130. ERA "")
  131. (define-simple-langinfo-mapping locale-era-year
  132. ERA_YEAR "")
  133. (define-simple-langinfo-mapping locale-era-date+time-format
  134. ERA_D_T_FMT "")
  135. (define-simple-langinfo-mapping locale-era-date-format
  136. ERA_D_FMT "")
  137. (define-simple-langinfo-mapping locale-era-time-format
  138. ERA_T_FMT "")
  139. ;;;
  140. ;;; Monetary information.
  141. ;;;
  142. ;; Define a procedure NAME that gets item LOCAL-ITEM or INTL-ITEM,
  143. ;; depending on whether the caller asked for the international version
  144. ;; or not. Since Gnulib's `nl_langinfo' module doesn't guarantee that
  145. ;; all these items are available, use DEFAULT/LOCAL and DEFAULT/INTL as
  146. ;; default values when the system does not support them.
  147. (define-macro (define-monetary-langinfo-mapping name local-item intl-item
  148. default/local default/intl)
  149. (let ((body
  150. (let ((intl (if (defined? intl-item)
  151. `(apply nl-langinfo ,intl-item locale)
  152. default/intl))
  153. (local (if (defined? local-item)
  154. `(apply nl-langinfo ,local-item locale)
  155. default/local)))
  156. `(if intl? ,intl ,local))))
  157. `(define (,name intl? . locale)
  158. ,body)))
  159. ;; FIXME: How can we use ALT_DIGITS?
  160. (define-monetary-langinfo-mapping locale-currency-symbol
  161. CRNCYSTR INT_CURR_SYMBOL
  162. "-" "")
  163. (define-monetary-langinfo-mapping locale-monetary-fractional-digits
  164. FRAC_DIGITS INT_FRAC_DIGITS
  165. 2 2)
  166. (define-simple-langinfo-mapping locale-monetary-positive-sign
  167. POSITIVE_SIGN "+")
  168. (define-simple-langinfo-mapping locale-monetary-negative-sign
  169. NEGATIVE_SIGN "-")
  170. (define-simple-langinfo-mapping locale-monetary-decimal-point
  171. MON_DECIMAL_POINT "")
  172. (define-simple-langinfo-mapping locale-monetary-thousands-separator
  173. MON_THOUSANDS_SEP "")
  174. (define-simple-langinfo-mapping locale-monetary-digit-grouping
  175. MON_GROUPING '())
  176. (define-monetary-langinfo-mapping locale-currency-symbol-precedes-positive?
  177. P_CS_PRECEDES INT_P_CS_PRECEDES
  178. #t #t)
  179. (define-monetary-langinfo-mapping locale-currency-symbol-precedes-negative?
  180. N_CS_PRECEDES INT_N_CS_PRECEDES
  181. #t #t)
  182. (define-monetary-langinfo-mapping locale-positive-separated-by-space?
  183. ;; Whether a space should be inserted between a positive amount and the
  184. ;; currency symbol.
  185. P_SEP_BY_SPACE INT_P_SEP_BY_SPACE
  186. #t #t)
  187. (define-monetary-langinfo-mapping locale-negative-separated-by-space?
  188. ;; Whether a space should be inserted between a negative amount and the
  189. ;; currency symbol.
  190. N_SEP_BY_SPACE INT_N_SEP_BY_SPACE
  191. #t #t)
  192. (define-monetary-langinfo-mapping locale-positive-sign-position
  193. ;; Position of the positive sign wrt. currency symbol and quantity in a
  194. ;; monetary amount.
  195. P_SIGN_POSN INT_P_SIGN_POSN
  196. 'unspecified 'unspecified)
  197. (define-monetary-langinfo-mapping locale-negative-sign-position
  198. ;; Position of the negative sign wrt. currency symbol and quantity in a
  199. ;; monetary amount.
  200. N_SIGN_POSN INT_N_SIGN_POSN
  201. 'unspecified 'unspecified)
  202. (define (%number-integer-part int grouping separator)
  203. ;; Process INT (a string denoting a number's integer part) and return a new
  204. ;; string with digit grouping and separators according to GROUPING (a list,
  205. ;; potentially circular) and SEPARATOR (a string).
  206. ;; Process INT from right to left.
  207. (let loop ((int int)
  208. (grouping grouping)
  209. (result '()))
  210. (cond ((string=? int "") (apply string-append result))
  211. ((null? grouping) (apply string-append int result))
  212. (else
  213. (let* ((len (string-length int))
  214. (cut (min (car grouping) len)))
  215. (loop (substring int 0 (- len cut))
  216. (cdr grouping)
  217. (let ((sub (substring int (- len cut) len)))
  218. (if (> len cut)
  219. (cons* separator sub result)
  220. (cons sub result)))))))))
  221. (define (add-monetary-sign+currency amount figure intl? locale)
  222. ;; Add a sign and currency symbol around FIGURE. FIGURE should be a
  223. ;; formatted unsigned amount (a string) representing AMOUNT.
  224. (let* ((positive? (> amount 0))
  225. (sign
  226. (cond ((> amount 0) (locale-monetary-positive-sign locale))
  227. ((< amount 0) (locale-monetary-negative-sign locale))
  228. (else "")))
  229. (currency (locale-currency-symbol intl? locale))
  230. (currency-precedes?
  231. (if positive?
  232. locale-currency-symbol-precedes-positive?
  233. locale-currency-symbol-precedes-negative?))
  234. (separated?
  235. (if positive?
  236. locale-positive-separated-by-space?
  237. locale-negative-separated-by-space?))
  238. (sign-position
  239. (if positive?
  240. locale-positive-sign-position
  241. locale-negative-sign-position))
  242. (currency-space
  243. (if (separated? intl? locale) " " ""))
  244. (append-currency
  245. (lambda (amt)
  246. (if (currency-precedes? intl? locale)
  247. (string-append currency currency-space amt)
  248. (string-append amt currency-space currency)))))
  249. (case (sign-position intl? locale)
  250. ((parenthesize)
  251. (string-append "(" (append-currency figure) ")"))
  252. ((sign-before)
  253. (string-append sign (append-currency figure)))
  254. ((sign-after unspecified)
  255. ;; following glibc's recommendation for `unspecified'.
  256. (if (currency-precedes? intl? locale)
  257. (string-append currency currency-space sign figure)
  258. (string-append figure currency-space currency sign)))
  259. ((sign-before-currency-symbol)
  260. (if (currency-precedes? intl? locale)
  261. (string-append sign currency currency-space figure)
  262. (string-append figure currency-space sign currency))) ;; unlikely
  263. ((sign-after-currency-symbol)
  264. (if (currency-precedes? intl? locale)
  265. (string-append currency sign currency-space figure)
  266. (string-append figure currency-space currency sign)))
  267. (else
  268. (error "unsupported sign position" (sign-position intl? locale))))))
  269. (define* (monetary-amount->locale-string amount intl?
  270. #:optional (locale %global-locale))
  271. "Convert @var{amount} (an inexact) into a string according to the cultural
  272. conventions of either @var{locale} (a locale object) or the current locale.
  273. If @var{intl?} is true, then the international monetary format for the given
  274. locale is used."
  275. (let* ((fraction-digits
  276. (or (locale-monetary-fractional-digits intl? locale) 2))
  277. (decimal-part
  278. (lambda (dec)
  279. (if (or (string=? dec "") (eq? 0 fraction-digits))
  280. ""
  281. (string-append (locale-monetary-decimal-point locale)
  282. (if (< fraction-digits (string-length dec))
  283. (substring dec 0 fraction-digits)
  284. dec)))))
  285. (external-repr (number->string (if (> amount 0) amount (- amount))))
  286. (int+dec (string-split external-repr #\.))
  287. (int (car int+dec))
  288. (dec (decimal-part (if (null? (cdr int+dec))
  289. ""
  290. (cadr int+dec))))
  291. (grouping (locale-monetary-digit-grouping locale))
  292. (separator (locale-monetary-thousands-separator locale)))
  293. (add-monetary-sign+currency amount
  294. (string-append
  295. (%number-integer-part int grouping
  296. separator)
  297. dec)
  298. intl? locale)))
  299. ;;;
  300. ;;; Number formatting.
  301. ;;;
  302. (define-simple-langinfo-mapping locale-digit-grouping
  303. GROUPING '())
  304. (define-simple-langinfo-mapping locale-decimal-point
  305. RADIXCHAR ".")
  306. (define-simple-langinfo-mapping locale-thousands-separator
  307. THOUSEP "")
  308. (define* (number->locale-string number
  309. #:optional (fraction-digits #t)
  310. (locale %global-locale))
  311. "Convert @var{number} (an inexact) into a string according to the cultural
  312. conventions of either @var{locale} (a locale object) or the current locale.
  313. Optionally, @var{fraction-digits} may be bound to an integer specifying the
  314. number of fractional digits to be displayed."
  315. (let* ((sign
  316. (cond ((> number 0) "")
  317. ((< number 0) "-")
  318. (else "")))
  319. (decimal-part
  320. (lambda (dec)
  321. (if (or (string=? dec "") (eq? 0 fraction-digits))
  322. ""
  323. (string-append (locale-decimal-point locale)
  324. (if (and (integer? fraction-digits)
  325. (< fraction-digits
  326. (string-length dec)))
  327. (substring dec 0 fraction-digits)
  328. dec))))))
  329. (let* ((external-repr (number->string (if (> number 0)
  330. number
  331. (- number))))
  332. (int+dec (string-split external-repr #\.))
  333. (int (car int+dec))
  334. (dec (decimal-part (if (null? (cdr int+dec))
  335. ""
  336. (cadr int+dec))))
  337. (grouping (locale-digit-grouping locale))
  338. (separator (locale-thousands-separator locale)))
  339. (string-append sign
  340. (%number-integer-part int grouping separator)
  341. dec))))
  342. ;;;
  343. ;;; Miscellaneous.
  344. ;;;
  345. (define-simple-langinfo-mapping locale-yes-regexp
  346. YESEXPR "^[yY]")
  347. (define-simple-langinfo-mapping locale-no-regexp
  348. NOEXPR "^[nN]")
  349. ;; `YESSTR' and `NOSTR' are considered deprecated so we don't provide them.
  350. ;;; i18n.scm ends here