;;;; i18n.test --- Exercise the i18n API. -*- coding: utf-8; mode: scheme; -*- ;;;; ;;;; Copyright (C) 2006-2007,2009-2019,2021 Free Software Foundation, Inc. ;;;; Author: Ludovic Courtès ;;;; ;;;; 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 (define-module (test-suite i18n) #:use-module (ice-9 i18n) #:use-module (ice-9 format) #:use-module (srfi srfi-1) #:use-module (test-suite lib)) ;; Start from a pristine locale state. (setlocale LC_ALL "C") (define exception:locale-error (cons 'system-error "Failed to install locale")) (with-test-prefix "locale objects" (pass-if "make-locale (2 args)" (not (not (make-locale LC_ALL "C")))) (pass-if "make-locale (2 args, list)" (not (not (make-locale (list LC_COLLATE LC_NUMERIC) "C")))) (pass-if "make-locale (3 args)" (not (not (make-locale (list LC_COLLATE) "C" (make-locale (list LC_NUMERIC) "C"))))) (pass-if-exception "make-locale with unknown locale" exception:locale-error (make-locale LC_ALL "does-not-exist")) (pass-if "locale?" (and (locale? (make-locale (list LC_ALL) "C")) (locale? (make-locale (list LC_TIME LC_NUMERIC) "C" (make-locale (list LC_CTYPE) "C"))))) (pass-if "%global-locale" (and (locale? %global-locale)) (locale? (make-locale (list LC_MONETARY) "C" %global-locale)))) (with-test-prefix "text collation (English)" (pass-if "string-locale?" (under-french-locale-or-unresolved (lambda () (and (string-locale-ci? "HiVeR" "été" %french-locale))))) (pass-if "string-locale-ci<>? (wide strings)" (under-french-utf8-locale-or-unresolved (lambda () ;; One of the strings is UCS-4, the other is Latin-1. (and (string-locale-ci? "Œuf" "œdÈMe" %french-utf8-locale))))) (pass-if "string-locale-ci<>? (wide and narrow strings)" (under-french-utf8-locale-or-unresolved (lambda () ;; One of the strings is UCS-4, the other is Latin-1. (and (string-locale-ci>? "Œdème" "odyssée" %french-utf8-locale) (string-locale-ci?" (under-french-locale-or-unresolved (lambda () (and (char-locale-ci? #\h #\É %french-locale))))) (pass-if "char-locale-ci<>? (wide)" (under-french-utf8-locale-or-unresolved (lambda () (and (char-locale-ci? #\Œ #\e %french-utf8-locale)))))) (with-test-prefix "text collation (German)" (pass-if "string-locale-ci=?" (under-german-utf8-locale-or-unresolved (lambda () (let ((de (make-locale LC_ALL %german-utf8-locale-name))) (string-locale-ci=? "Straße" "STRASSE")))))) (with-test-prefix "text collation (Greek)" (pass-if "string-locale-ci=?" (under-greek-utf8-locale-or-unresolved (lambda () (let ((gr (make-locale LC_ALL %greek-utf8-locale-name))) (string-locale-ci=? "ΧΑΟΣ" "χαος" gr)))))) (with-test-prefix "text collation (Czech)" (pass-if "string-locale. For ;; now, just skip it if it fails (XXX). (or (and (string-locale>? "chxxx" "cxxx") (string-locale>? "chxxx" "hxxx") (string-localeinteger" (call-with-values (lambda () (locale-string->integer "123")) (lambda (result char-count) (and (equal? result 123) (equal? char-count 3))))) (pass-if "locale-string->inexact" (call-with-values (lambda () (locale-string->inexact "123.456" (make-locale (list LC_NUMERIC) "C"))) (lambda (result char-count) (and (equal? result 123.456) (equal? char-count 7))))) (pass-if "locale-string->inexact (French)" (under-french-locale-or-unresolved (lambda () (call-with-values (lambda () (locale-string->inexact "123,456" %french-locale)) (lambda (result char-count) (and (equal? result 123.456) (equal? char-count 7)))))))) ;;; ;;; `nl-langinfo' ;;; (setlocale LC_ALL "C") (define %c-locale (make-locale LC_ALL "C")) (define %english-days '("Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday")) (define (every? . args) (not (not (apply every args)))) (with-test-prefix "nl-langinfo et al." (pass-if "locale-day (1 arg)" (every? equal? %english-days (map locale-day (map 1+ (iota 7))))) (pass-if "locale-day (2 args)" (every? equal? %english-days (map (lambda (day) (locale-day day %c-locale)) (map 1+ (iota 7))))) (pass-if "locale-day (2 args, using `%global-locale')" (every? equal? %english-days (map (lambda (day) (locale-day day %global-locale)) (map 1+ (iota 7))))) (pass-if "locale-day (French)" (under-french-locale-or-unresolved (lambda () (let ((result (locale-day 3 %french-locale))) (and (string? result) (string-ci=? result "mardi")))))) (pass-if "locale-day (French, using `%global-locale')" ;; Make sure `%global-locale' captures the current locale settings as ;; installed using `setlocale'. (under-french-locale-or-unresolved (lambda () (dynamic-wind (lambda () (setlocale LC_TIME %french-locale-name)) (lambda () (let* ((fr (make-locale (list LC_MONETARY) "C" %global-locale)) (result (locale-day 3 fr))) (setlocale LC_ALL "C") (and (string? result) (string-ci=? result "mardi")))) (lambda () (setlocale LC_ALL "C")))))) (pass-if "default locale" ;; Make sure the default locale does not capture the current locale ;; settings as installed using `setlocale'. The default locale should be ;; "C". (under-french-locale-or-unresolved (lambda () (dynamic-wind (lambda () (setlocale LC_ALL %french-locale-name)) (lambda () (let* ((locale (make-locale (list LC_MONETARY) "C")) (result (locale-day 3 locale))) (setlocale LC_ALL "C") (and (string? result) (string-ci=? result "Tuesday")))) (lambda () (setlocale LC_ALL "C")))))) (pass-if "locale-am-string" (not (not (member (locale-am-string) '("AM" "am" "A.M." "a.m."))))) (pass-if "locale-am-string (greek)" (under-greek-utf8-locale-or-unresolved (lambda () (not (not (member (locale-am-string %greek-utf8-locale) '("ΠΜ" "πμ" "Π.Μ." "π.μ."))))))) (pass-if "locale-pm-string" (not (not (member (locale-pm-string) '("PM" "pm" "P.M." "p.m."))))) (pass-if "locale-pm-string (Greek)" (under-greek-utf8-locale-or-unresolved (lambda () (not (not (member (locale-pm-string %greek-utf8-locale) '("ΜΜ" "μμ" "Μ.Μ." "μ.μ."))))))) (pass-if "locale-digit-grouping" ;; In the C locale, there is no rule for grouping. (null? (locale-digit-grouping))) (pass-if "locale-digit-grouping (French)" (under-french-locale-or-unresolved (lambda () ;; All systems that have a GROUPING nl_item should know ;; that French numbers are grouped in 3 digit chunks. ;; Those systems that have no GROUPING nl_item may use ;; the hard-coded default of no grouping. (let ((result (locale-digit-grouping %french-locale))) (cond ((null? result) (throw 'unresolved)) ((eqv? 3 (false-if-exception (car result))) #t) (else #f)))))) (pass-if "locale-positive-separated-by-space?" ;; In any locale, this must be a boolean. (let ((result (locale-positive-separated-by-space? #f))) (or (eqv? #t result) (eqv? #f result)))) (pass-if "locale-positive-separated-by-space? (international)" ;; In any locale, this must be a boolean. (let ((result (locale-positive-separated-by-space? #t))) (or (eqv? #t result) (eqv? #f result)))) (pass-if "locale-monetary-grouping" ;; In the C locale, there is no rule for grouping of digits ;; of monetary values. (null? (locale-monetary-grouping))) (pass-if "locale-monetary-grouping (French)" (under-french-utf8-locale-or-unresolved (lambda () ;; All systems that have a MON_GROUPING nl_item should know ;; that French monetary values are grouped in 3 digit chunks. ;; Those systems that have no MON_GROUPING nl_item may use the ;; hard-coded default of no grouping. (let ((result (locale-monetary-grouping %french-utf8-locale))) (cond ((null? result) (throw 'unresolved)) ((eqv? 3 (false-if-exception (car result))) #t) (else #f))))))) ;;; ;;; Numbers. ;;; (define (french-number-string=? expected result) ;; Return true if RESULT is equal to EXPECTED, modulo white space. ;; This is meant to deal with French locales: glibc 2.27+ uses ;; NO-BREAK SPACE to separate 3-digit groups, whereas earlier versions ;; used SPACE. (or (string=? expected result) (string=? (string-map (lambda (chr) (case chr ((#\space) #\240) (else chr))) ;NO-BREAK SPACE expected) result))) (with-test-prefix "number->locale-string" ;; We assume the global locale is "C" at this point. (with-test-prefix "C" (pass-if-equal "no thousand separator" "" ;; Unlike in English, the "C" locale has no thousand separator. ;; If this doesn't hold, the following tests will fail. (locale-thousands-separator)) (pass-if-equal "integer" "123456" (number->locale-string 123456)) (pass-if-equal "fraction" "1234.567" (number->locale-string 1234.567)) (pass-if-equal "fraction, 1 digit" "1234.6" (number->locale-string 1234.567 1)) (pass-if-equal "fraction, 10 digits" "0.0000300000" (number->locale-string .00003 10)) (pass-if-equal "trailing zeros" "-10.00000" (number->locale-string -10.0 5)) (pass-if-equal "positive inexact zero, 1 digit" "0.0" (number->locale-string .0 1))) (with-test-prefix "French" (pass-if "integer" (under-french-locale-or-unresolved (lambda () (let ((fr (make-locale LC_ALL %french-locale-name))) (french-number-string=? "123 456" (number->locale-string 123456 #t fr)))))) (pass-if "negative integer" (under-french-locale-or-unresolved (lambda () (let ((fr (make-locale LC_ALL %french-locale-name))) (french-number-string=? "-1 234 567" (number->locale-string -1234567 #t fr)))))) (pass-if "fraction" (under-french-locale-or-unresolved (lambda () (let ((fr (make-locale LC_ALL %french-locale-name))) (french-number-string=? "1 234,567" (number->locale-string 1234.567 #t fr)))))) (pass-if "fraction, 1 digit" (under-french-locale-or-unresolved (lambda () (let ((fr (make-locale LC_ALL %french-locale-name))) (french-number-string=? "1 234,6" (number->locale-string 1234.567 1 fr)))))))) (with-test-prefix "format ~h" ;; Some systems like Darwin lack the `GROUPING' nl_item, and thus ;; `locale-digit-grouping' defaults to '(); skip the tests in that ;; case. (with-test-prefix "French" (pass-if "12345.678" (under-french-locale-or-unresolved (lambda () (if (null? (locale-digit-grouping %french-locale)) (throw 'unresolved) (french-number-string=? "12 345,678" (format #f "~:h" 12345.678 %french-locale))))))) (with-test-prefix "English" (pass-if-equal "12345.678" "12,345.678" (under-american-english-locale-or-unresolved (lambda () (if (null? (locale-digit-grouping %american-english-locale)) (throw 'unresolved) (format #f "~:h" 12345.678 %american-english-locale))))))) (with-test-prefix "monetary-amount->locale-string" (with-test-prefix "French" (pass-if "integer" (under-french-locale-or-unresolved (lambda () (let* ((fr (make-locale LC_ALL %french-locale-name)) (str (string-trim-both (monetary-amount->locale-string 123456 #f fr)))) ;; Check for both NO-BREAK SPACE and SPACE. (or (string=? "123 456,00 +EUR" str) (string=? "123 456,00 +EUR" str)))))) (pass-if "fraction" (under-french-locale-or-unresolved (lambda () (let* ((fr (make-locale LC_ALL %french-locale-name)) (str (monetary-amount->locale-string 1234.567 #t fr))) ;; Check for both NO-BREAK SPACE and SPACE. (or (string=? "1 234,57 EUR " str) (string=? "1 234,57 EUR " str)))))) (pass-if-equal "positive inexact zero" "0,00 +EUR" (under-french-locale-or-unresolved (lambda () (let ((fr (make-locale LC_ALL %french-locale-name))) (string-trim-both (monetary-amount->locale-string 0. #f fr)))))) (pass-if-equal "one cent" "0,01 EUR " (under-french-locale-or-unresolved (lambda () (let ((fr (make-locale LC_ALL %french-locale-name))) (monetary-amount->locale-string .01 #t fr))))) (pass-if-equal "very little money" "0,00 EUR " (under-french-locale-or-unresolved (lambda () (let ((fr (make-locale LC_ALL %french-locale-name))) (monetary-amount->locale-string .00003 #t fr)))))))