123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- ;;;; i18n.scm --- internationalization support -*- coding: utf-8 -*-
- ;;;; Copyright (C) 2006, 2007, 2009, 2010, 2012,
- ;;;; 2017, 2019 Free Software Foundation, Inc.
- ;;;;
- ;;;; This library is free software; you can redistribute it and/or
- ;;;; modify it under the terms of the GNU Lesser General Public
- ;;;; License as published by the Free Software Foundation; either
- ;;;; version 3 of the License, or (at your option) any later version.
- ;;;;
- ;;;; This library 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
- ;;;; Lesser General Public License for more details.
- ;;;;
- ;;;; You should have received a copy of the GNU Lesser General Public
- ;;;; License along with this library; if not, write to the Free Software
- ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- ;;; Author: Ludovic Courtès <ludo@gnu.org>
- ;;; Commentary:
- ;;;
- ;;; This module provides a number of routines that support
- ;;; internationalization (e.g., locale-dependent text collation, character
- ;;; mapping, etc.). It also defines `locale' objects, representing locale
- ;;; settings, that may be passed around to most of these procedures.
- ;;;
- ;;; Code:
- (define-module (ice-9 i18n)
- :use-module (ice-9 optargs)
- :export (;; `locale' type
- make-locale locale?
- %global-locale
- ;; text collation
- string-locale<? string-locale>?
- string-locale-ci<? string-locale-ci>? string-locale-ci=?
- char-locale<? char-locale>?
- char-locale-ci<? char-locale-ci>? char-locale-ci=?
- ;; character mapping
- char-locale-downcase char-locale-upcase char-locale-titlecase
- string-locale-downcase string-locale-upcase string-locale-titlecase
- ;; reading numbers
- locale-string->integer locale-string->inexact
- ;; charset/encoding
- locale-encoding
- ;; days and months
- locale-day-short locale-day locale-month-short locale-month
- ;; date and time
- locale-am-string locale-pm-string
- locale-date+time-format locale-date-format locale-time-format
- locale-time+am/pm-format
- locale-era locale-era-year
- locale-era-date-format locale-era-date+time-format
- locale-era-time-format
- ;; monetary
- locale-currency-symbol
- locale-monetary-decimal-point locale-monetary-thousands-separator
- locale-monetary-grouping locale-monetary-fractional-digits
- locale-currency-symbol-precedes-positive?
- locale-currency-symbol-precedes-negative?
- locale-positive-separated-by-space?
- locale-negative-separated-by-space?
- locale-monetary-positive-sign locale-monetary-negative-sign
- locale-positive-sign-position locale-negative-sign-position
- monetary-amount->locale-string
- ;; number formatting
- locale-digit-grouping locale-decimal-point
- locale-thousands-separator
- number->locale-string
- ;; miscellaneous
- locale-yes-regexp locale-no-regexp
- ;; debugging
- %locale-dump))
- (eval-when (expand load eval)
- (load-extension (string-append "libguile-" (effective-version))
- "scm_init_i18n"))
- ;;;
- ;;; Charset/encoding.
- ;;;
- (define (locale-encoding . locale)
- (apply nl-langinfo CODESET locale))
- ;;;
- ;;; Months and days.
- ;;;
- ;; Helper macro: Define a procedure named NAME that maps its argument to
- ;; NL-ITEMS. Gnulib guarantees that these items are available.
- (define-macro (define-vector-langinfo-mapping name nl-items)
- (let* ((item-count (length nl-items))
- (defines `(define %nl-items (vector #f ,@nl-items)))
- (make-body (lambda (result)
- `(if (and (integer? item) (exact? item))
- (if (and (>= item 1) (<= item ,item-count))
- ,result
- (throw 'out-of-range "out of range" item))
- (throw 'wrong-type-arg "wrong argument type" item)))))
- `(define (,name item . locale)
- ,defines
- ,(make-body '(apply nl-langinfo (vector-ref %nl-items item) locale)))))
- (define-vector-langinfo-mapping locale-day-short
- (ABDAY_1 ABDAY_2 ABDAY_3 ABDAY_4 ABDAY_5 ABDAY_6 ABDAY_7))
- (define-vector-langinfo-mapping locale-day
- (DAY_1 DAY_2 DAY_3 DAY_4 DAY_5 DAY_6 DAY_7))
- (define-vector-langinfo-mapping locale-month-short
- (ABMON_1 ABMON_2 ABMON_3 ABMON_4 ABMON_5 ABMON_6
- ABMON_7 ABMON_8 ABMON_9 ABMON_10 ABMON_11 ABMON_12))
- (define-vector-langinfo-mapping locale-month
- (MON_1 MON_2 MON_3 MON_4 MON_5 MON_6 MON_7 MON_8 MON_9 MON_10 MON_11 MON_12))
- ;;;
- ;;; Date and time.
- ;;;
- ;; Define a procedure NAME that gets langinfo item ITEM. Gnulib's
- ;; `nl_langinfo' does not guarantee that all these items are supported
- ;; (for instance, `GROUPING' is lacking on Darwin and Gnulib provides no
- ;; replacement), so use DEFAULT as the default value when ITEM is not
- ;; available.
- (define-macro (define-simple-langinfo-mapping name item default)
- (let ((body (if (defined? item)
- `(apply nl-langinfo ,item locale)
- default)))
- `(define (,name . locale)
- ,body)))
- (define-simple-langinfo-mapping locale-am-string
- AM_STR "AM")
- (define-simple-langinfo-mapping locale-pm-string
- PM_STR "PM")
- (define-simple-langinfo-mapping locale-date+time-format
- D_T_FMT "%a %b %e %H:%M:%S %Y")
- (define-simple-langinfo-mapping locale-date-format
- D_FMT "%m/%d/%y")
- (define-simple-langinfo-mapping locale-time-format
- T_FMT "%H:%M:%S")
- (define-simple-langinfo-mapping locale-time+am/pm-format
- T_FMT_AMPM "%I:%M:%S %p")
- (define-simple-langinfo-mapping locale-era
- ERA "")
- (define-simple-langinfo-mapping locale-era-year
- ERA_YEAR "")
- (define-simple-langinfo-mapping locale-era-date+time-format
- ERA_D_T_FMT "")
- (define-simple-langinfo-mapping locale-era-date-format
- ERA_D_FMT "")
- (define-simple-langinfo-mapping locale-era-time-format
- ERA_T_FMT "")
- ;;;
- ;;; Monetary information.
- ;;;
- ;; Define a procedure NAME that gets item LOCAL-ITEM or INTL-ITEM,
- ;; depending on whether the caller asked for the international version
- ;; or not. Since Gnulib's `nl_langinfo' module doesn't guarantee that
- ;; all these items are available, use DEFAULT/LOCAL and DEFAULT/INTL as
- ;; default values when the system does not support them.
- (define-macro (define-monetary-langinfo-mapping name local-item intl-item
- default/local default/intl)
- (let ((body
- (let ((intl (if (defined? intl-item)
- `(apply nl-langinfo ,intl-item locale)
- default/intl))
- (local (if (defined? local-item)
- `(apply nl-langinfo ,local-item locale)
- default/local)))
- `(if intl? ,intl ,local))))
- `(define (,name intl? . locale)
- ,body)))
- ;; FIXME: How can we use ALT_DIGITS?
- (define-monetary-langinfo-mapping locale-currency-symbol
- CRNCYSTR INT_CURR_SYMBOL
- "-" "")
- (define-monetary-langinfo-mapping locale-monetary-fractional-digits
- FRAC_DIGITS INT_FRAC_DIGITS
- 2 2)
- (define-simple-langinfo-mapping locale-monetary-positive-sign
- POSITIVE_SIGN "+")
- (define-simple-langinfo-mapping locale-monetary-negative-sign
- NEGATIVE_SIGN "-")
- (define-simple-langinfo-mapping locale-monetary-decimal-point
- MON_DECIMAL_POINT ".")
- (define-simple-langinfo-mapping locale-monetary-thousands-separator
- MON_THOUSANDS_SEP "")
- (define-simple-langinfo-mapping locale-monetary-grouping
- MON_GROUPING '())
- (define-monetary-langinfo-mapping locale-currency-symbol-precedes-positive?
- P_CS_PRECEDES INT_P_CS_PRECEDES
- #t #t)
- (define-monetary-langinfo-mapping locale-currency-symbol-precedes-negative?
- N_CS_PRECEDES INT_N_CS_PRECEDES
- #t #t)
- (define-monetary-langinfo-mapping locale-positive-separated-by-space?
- ;; Whether a space should be inserted between a positive amount and the
- ;; currency symbol.
- P_SEP_BY_SPACE INT_P_SEP_BY_SPACE
- #t #t)
- (define-monetary-langinfo-mapping locale-negative-separated-by-space?
- ;; Whether a space should be inserted between a negative amount and the
- ;; currency symbol.
- N_SEP_BY_SPACE INT_N_SEP_BY_SPACE
- #t #t)
- (define-monetary-langinfo-mapping locale-positive-sign-position
- ;; Position of the positive sign wrt. currency symbol and quantity in a
- ;; monetary amount.
- P_SIGN_POSN INT_P_SIGN_POSN
- 'unspecified 'unspecified)
- (define-monetary-langinfo-mapping locale-negative-sign-position
- ;; Position of the negative sign wrt. currency symbol and quantity in a
- ;; monetary amount.
- N_SIGN_POSN INT_N_SIGN_POSN
- 'unspecified 'unspecified)
- (define (integer->string number)
- "Return a string representing NUMBER, an integer, written in base 10."
- (define (digit->char digit)
- (integer->char (+ digit (char->integer #\0))))
- (if (zero? number)
- "0"
- (let loop ((number number)
- (digits '()))
- (if (zero? number)
- (list->string digits)
- (loop (quotient number 10)
- (cons (digit->char (modulo number 10))
- digits))))))
- (define (number-decimal-string number digit-count)
- "Return a string representing the decimal part of NUMBER. When
- DIGIT-COUNT is an integer, return exactly DIGIT-COUNT digits; when
- DIGIT-COUNT is #t, return as many decimals as necessary, up to an
- arbitrary limit."
- (define max-decimals
- 5)
- ;; XXX: This is brute-force and could be improved by following one of
- ;; the "Printing Floating-Point Numbers Quickly and Accurately"
- ;; papers.
- (if (integer? digit-count)
- (let ((number (* (expt 10 digit-count)
- (- number (floor number)))))
- (string-pad (integer->string (round (inexact->exact number)))
- digit-count
- #\0))
- (let loop ((decimals 0))
- (let ((number' (* number (expt 10 decimals))))
- (if (or (= number' (floor number'))
- (>= decimals max-decimals))
- (let* ((fraction (- number'
- (* (floor number)
- (expt 10 decimals))))
- (str (integer->string
- (round (inexact->exact fraction)))))
- (if (zero? fraction)
- ""
- str))
- (loop (+ decimals 1)))))))
- (define (%number-integer-part int grouping separator)
- ;; Process INT (a string denoting a number's integer part) and return a new
- ;; string with digit grouping and separators according to GROUPING (a list,
- ;; potentially circular) and SEPARATOR (a string).
- ;; Process INT from right to left.
- (let loop ((int int)
- (grouping grouping)
- (result '()))
- (cond ((string=? int "") (apply string-append result))
- ((null? grouping) (apply string-append int result))
- (else
- (let* ((len (string-length int))
- (cut (min (car grouping) len)))
- (loop (substring int 0 (- len cut))
- (cdr grouping)
- (let ((sub (substring int (- len cut) len)))
- (if (> len cut)
- (cons* separator sub result)
- (cons sub result)))))))))
- (define (add-monetary-sign+currency amount figure intl? locale)
- ;; Add a sign and currency symbol around FIGURE. FIGURE should be a
- ;; formatted unsigned amount (a string) representing AMOUNT.
- (let* ((positive? (> amount 0))
- (sign
- (cond ((> amount 0) (locale-monetary-positive-sign locale))
- ((< amount 0) (locale-monetary-negative-sign locale))
- (else "")))
- (currency (locale-currency-symbol intl? locale))
- (currency-precedes?
- (if positive?
- locale-currency-symbol-precedes-positive?
- locale-currency-symbol-precedes-negative?))
- (separated?
- (if positive?
- locale-positive-separated-by-space?
- locale-negative-separated-by-space?))
- (sign-position
- (if positive?
- locale-positive-sign-position
- locale-negative-sign-position))
- (currency-space
- (if (separated? intl? locale) " " ""))
- (append-currency
- (lambda (amt)
- (if (currency-precedes? intl? locale)
- (string-append currency currency-space amt)
- (string-append amt currency-space currency)))))
- (case (sign-position intl? locale)
- ((parenthesize)
- (string-append "(" (append-currency figure) ")"))
- ((sign-before)
- (string-append sign (append-currency figure)))
- ((sign-after unspecified)
- ;; following glibc's recommendation for `unspecified'.
- (if (currency-precedes? intl? locale)
- (string-append currency currency-space sign figure)
- (string-append figure currency-space currency sign)))
- ((sign-before-currency-symbol)
- (if (currency-precedes? intl? locale)
- (string-append sign currency currency-space figure)
- (string-append figure currency-space sign currency))) ;; unlikely
- ((sign-after-currency-symbol)
- (if (currency-precedes? intl? locale)
- (string-append currency sign currency-space figure)
- (string-append figure currency-space currency sign)))
- (else
- (error "unsupported sign position" (sign-position intl? locale))))))
- (define* (monetary-amount->locale-string amount intl?
- #:optional (locale %global-locale))
- "Convert @var{amount} (an inexact) into a string according to the cultural
- conventions of either @var{locale} (a locale object) or the current locale.
- If @var{intl?} is true, then the international monetary format for the given
- locale is used."
- (let* ((fraction-digits
- (or (locale-monetary-fractional-digits intl? locale) 2))
- (decimal-part
- (lambda (dec)
- (if (or (string=? dec "") (eq? 0 fraction-digits))
- ""
- (string-append (locale-monetary-decimal-point locale)
- (if (< fraction-digits (string-length dec))
- (substring dec 0 fraction-digits)
- dec)))))
- (int (integer->string (inexact->exact
- (floor (abs amount)))))
- (dec (decimal-part
- (number-decimal-string (abs amount)
- fraction-digits)))
- (grouping (locale-monetary-grouping locale))
- (separator (locale-monetary-thousands-separator locale)))
- (add-monetary-sign+currency amount
- (string-append
- (%number-integer-part int grouping
- separator)
- dec)
- intl? locale)))
- ;;;
- ;;; Number formatting.
- ;;;
- (define-simple-langinfo-mapping locale-digit-grouping
- GROUPING '())
- (define-simple-langinfo-mapping locale-decimal-point
- RADIXCHAR ".")
- (define-simple-langinfo-mapping locale-thousands-separator
- THOUSEP "")
- (define* (number->locale-string number
- #:optional (fraction-digits #t)
- (locale %global-locale))
- "Convert @var{number} (an inexact) into a string according to the cultural
- conventions of either @var{locale} (a locale object) or the current locale.
- By default, print as many fractional digits as necessary, up to an upper bound.
- Optionally, @var{fraction-digits} may be bound to an integer specifying the
- number of fractional digits to be displayed."
- (let* ((sign
- (cond ((> number 0) "")
- ((< number 0) "-")
- (else "")))
- (decimal-part
- (lambda (dec)
- (if (or (string=? dec "") (eq? 0 fraction-digits))
- ""
- (string-append (locale-decimal-point locale)
- (if (and (integer? fraction-digits)
- (< fraction-digits
- (string-length dec)))
- (substring dec 0 fraction-digits)
- dec))))))
- (let* ((int (integer->string (inexact->exact
- (floor (abs number)))))
- (dec (decimal-part
- (number-decimal-string (abs number)
- fraction-digits)))
- (grouping (locale-digit-grouping locale))
- (separator (locale-thousands-separator locale)))
- (string-append sign
- (%number-integer-part int grouping separator)
- dec))))
- ;;;
- ;;; Miscellaneous.
- ;;;
- (define-simple-langinfo-mapping locale-yes-regexp
- YESEXPR "^[yY]")
- (define-simple-langinfo-mapping locale-no-regexp
- NOEXPR "^[nN]")
- ;; `YESSTR' and `NOSTR' are considered deprecated so we don't provide them.
- ;;;
- ;;; Debugging
- ;;;
- (define (%locale-dump loc)
- "Given a locale, display an association list containing all the locale
- information.
- This procedure is intended for debugging locale problems, and should
- not be used in production code."
- (when (locale? loc)
- (list
- (cons 'encoding (locale-encoding loc))
- (cons 'day-short
- (map (lambda (n) (locale-day-short (1+ n) loc)) (iota 7)))
- (cons 'day
- (map (lambda (n) (locale-day (1+ n) loc)) (iota 7)))
- (cons 'month-short
- (map (lambda (n) (locale-month-short (1+ n) loc)) (iota 12)))
- (cons 'month
- (map (lambda (n) (locale-month (1+ n) loc)) (iota 12)))
- (cons 'am-string (locale-am-string loc))
- (cons 'pm-string (locale-pm-string loc))
- (cons 'date+time-format (locale-date+time-format loc))
- (cons 'date-format (locale-date-format loc))
- (cons 'time-format (locale-time-format loc))
- (cons 'time+am/pm-format (locale-time+am/pm-format loc))
- (cons 'era (locale-era loc))
- (cons 'era-year (locale-era-year loc))
- (cons 'era-date-format (locale-era-date-format loc))
- (cons 'era-date+time-format (locale-era-date+time-format loc))
- (cons 'era-time-format (locale-era-time-format loc))
- (cons 'currency-symbol
- (list (locale-currency-symbol #t loc)
- (locale-currency-symbol #f loc)))
- (cons 'monetary-decimal-point (locale-monetary-decimal-point loc))
- (cons 'monetary-thousands-separator (locale-monetary-thousands-separator loc))
- (cons 'monetary-grouping (locale-monetary-grouping loc))
- (cons 'monetary-fractional-digits
- (list (locale-monetary-fractional-digits #t loc)
- (locale-monetary-fractional-digits #f loc)))
- (cons 'currency-symbol-precedes-positive?
- (list (locale-currency-symbol-precedes-positive? #t loc)
- (locale-currency-symbol-precedes-positive? #f loc)))
- (cons 'currency-symbol-precedes-negative?
- (list (locale-currency-symbol-precedes-negative? #t loc)
- (locale-currency-symbol-precedes-negative? #f loc)))
- (cons 'positive-separated-by-space?
- (list (locale-positive-separated-by-space? #t loc)
- (locale-positive-separated-by-space? #f loc)))
- (cons 'negative-separated-by-space?
- (list (locale-negative-separated-by-space? #t loc)
- (locale-negative-separated-by-space? #f loc)))
- (cons 'monetary-positive-sign (locale-monetary-positive-sign loc))
- (cons 'monetary-negative-sign (locale-monetary-negative-sign loc))
- (cons 'positive-sign-position
- (list (locale-positive-sign-position #t loc)
- (locale-negative-sign-position #f loc)))
- (cons 'negative-sign-position
- (list (locale-negative-sign-position #t loc)
- (locale-negative-sign-position #f loc)))
- (cons 'digit-grouping (locale-digit-grouping loc))
- (cons 'decimal-point (locale-decimal-point loc))
- (cons 'thousands-separator (locale-thousands-separator loc))
- (cons 'locale-yes-regexp (locale-yes-regexp loc))
- (cons 'no-regexp (locale-no-regexp loc)))))
- ;;; i18n.scm ends here
|