Update of /project/cl-l10n/cvsroot/cl-l10n In directory clnet:/tmp/cvs-serv6511
Modified Files: cl-l10n.asd i18n.lisp load-locale.lisp locale.lisp package.lisp parse-time.lisp parsers.lisp printers.lisp Log Message: Merge attila.lendvai@gmail.com's changes, mostly i18n stuff
--- /project/cl-l10n/cvsroot/cl-l10n/cl-l10n.asd 2006/04/27 18:30:30 1.15 +++ /project/cl-l10n/cvsroot/cl-l10n/cl-l10n.asd 2006/06/08 09:38:19 1.16 @@ -28,7 +28,6 @@
(defmethod perform :after ((o load-op) (c (eql (find-system :cl-l10n)))) (provide 'cl-l10n)) -
(defmethod perform ((op test-op) (sys (eql (find-system :cl-l10n)))) (oos 'load-op :cl-l10n-tests) --- /project/cl-l10n/cvsroot/cl-l10n/i18n.lisp 2006/04/27 18:30:30 1.4 +++ /project/cl-l10n/cvsroot/cl-l10n/i18n.lisp 2006/06/08 09:38:19 1.5 @@ -2,68 +2,120 @@ ;; See the file LICENCE for licence information. (in-package :cl-l10n)
+#| +(defresources en + (indefinit-article-for (str) + ;; calculate "a"/"an" here + ) + (foo.bar "some constant")) + +then writing (indefinit-article-for "asdf") will call the locale-specific +implementation of that function + +|# + +(defvar *resources* (make-hash-table :test 'equal)) + +(defun clear-resources () + (setf *resources* (make-hash-table :test 'equal))) + +(defun resource-key (locale name) + (list (if (stringp locale) locale (locale-name locale)) + (if (stringp name) (string-downcase name) (string-downcase (symbol-name name))))) + +(define-condition resource-missing (warning) + ((name :accessor name-of :initarg :name))) + +(defun add-resource (locale name args body) + ;; store in resouce map + (setf (gethash (resource-key locale name) *resources*) + (if (and (= (length body) 1) + (stringp (first body))) + (first body) + (eval `(lambda ,args ,@body)))) + ;; make a function + (setf (symbol-function name) (eval `(lambda (&rest args) (lookup-resource ',name args)))) + name) + +(defun %lookup-resource (locale name args) + (declare (type locale locale) + (type (or symbol string) name)) + (let* ((key (resource-key locale name))) + (multiple-value-bind (resource found) + (gethash key *resources*) + (unless found + ;; try again with the default locale for the language + (setf key (resource-key (canonical-locale-name-from (first (split "_" (locale-name locale)))) name)) + (setf resource (gethash key *resources*))) + ;; dispatch on resource type + (cond ((functionp resource) + (apply resource args)) + ;; literal + ((not (null resource)) + resource))))) + +(defun lookup-resource (name args &key (warn-if-missing t) (fallback-to-name t)) + (loop for locale in (if (consp *locale*) *locale* (list *locale*)) do + (let ((result (funcall '%lookup-resource locale name args))) + (when result + (return-from lookup-resource (values result t))))) + (resource-not-found name warn-if-missing fallback-to-name)) + +(defun lookup-resource-without-fallback (locale name args &key (warn-if-missing t) (fallback-to-name t)) + (aif (%lookup-resource locale name args) + it + (resource-not-found name warn-if-missing fallback-to-name))) + +(defun resource-not-found (name warn-if-missing fallback-to-name) + (if warn-if-missing + (signal 'resource-missing :name name)) + (values (if fallback-to-name + (string-downcase (string name))) + nil)) + +(defmacro defresources (locale &body resources) + (let ((locale-name (canonical-locale-name-from locale))) + (cons 'progn + (loop for resource in resources + if (= 2 (length resource)) + collect `(add-resource ,locale-name + ',(first resource) nil ',(cdr resource)) + else + collect `(add-resource ,locale-name + ',(first resource) ',(second resource) ',(cddr resource)))))) + +(defmacro enable-sharpquote-reader () + "Enable quote reader for the rest of the file (being loaded or compiled). +#"my i18n text" parts will be replaced by a lookup-resource call for the string. +Be careful when using in different situations, because it modifies *readtable*." + ;; The standard sais that *readtable* is restored after loading/compiling a file, + ;; so we make a copy and alter that. The effect is that it will be enabled + ;; for the rest of the file being processed. + `(eval-when (:compile-toplevel :execute) + (setf *readtable* (copy-readtable *readtable*)) + (%enable-sharpquote-reader))) + +(defun %enable-sharpquote-reader () + (set-dispatch-macro-character + ## #" + #'(lambda (s c1 c2) + (declare (ignore c2)) + (unread-char c1 s) + `(lookup-resource ,(read s) nil)))) + +(defun with-sharpquote-syntax () + "To be used with the curly reader from arnesi: {with-sharpquote-reader (foo #"locale-specific") }" + (lambda (handler) + (%enable-sharpquote-reader) + `(progn ,@(funcall handler))))
-;; (defparameter bundle (make-instance 'bundle))
-;; (add-resources (bundle "af_") -;; "showtime" "Dankie, die tyd is ~:@U~%")
-;; ;; an empty string as the locale matcher becomes the default -;; (add-resources (bundle "") -;; "showtime" "Thanks, the time is ~:@U~%") - -;; (set-dispatch-macro-character -;; ## #\i -;; #'(lambda (s c1 c2) -;; (declare (ignore c2)) -;; `(cl-l10n:gettext ,(read s) bundle))) - -;; or this -;; (defmacro _ (text) -;; `(cl-l10n:gettext ,text bundle)) - -;; (defun timey () -;; (format t #i"showtime" (get-universal-time))) - -(defclass bundle () - ((resources :accessor resources :initform (make-hash-table :test #'equal)))) - -(defgeneric add-resource (bundle from to lang)) -(defmethod add-resource (bundle from to lang) - (aif (assoc lang (gethash from (resources bundle)) :test #'equal) - (setf (cdr it) to) - (pushnew (cons lang to) (gethash from (resources bundle)) - :test #'equal)) - t) - -(defmacro add-resources ((bundle loc-name) &body args) - (with-gensyms (gloc gbundle) - `(let ((,gloc ,loc-name) (,gbundle ,bundle)) - ,@(mapcar #'(lambda (x) `(add-resource ,gbundle ,@x ,gloc)) - (group args 2))))) - -(defgeneric get-name (bundle name) - (:method ((bundle t) (name t)) - (gethash name (resources bundle)))) - -(defgeneric lookup-name (bundle name) - (:method ((bundle t) (name t)) - (when-let (name (get-name bundle name)) - ;; The match with the longest name is the most - ;; specific key. - (winner #'> - (load-time-value (compose #'length #'car)) - (remove-if-not #'(lambda (x) - (search (car x) - (locale-name *locale*))) - name))))) - -(defun gettext (name bundle &optional (loc *locale*)) - (let ((*locale* (locale-des->locale loc))) - (or (cdr (lookup-name bundle name)) - name))) +(defgeneric localize (object) + (:documentation "Override this generic method for various data types. Return (values result foundp)."))
+(defmethod localize ((str string)) + (lookup-resource str nil))
- - -;; EOF +(defmethod localize ((str symbol)) + (lookup-resource str nil)) --- /project/cl-l10n/cvsroot/cl-l10n/load-locale.lisp 2006/06/06 14:58:46 1.16 +++ /project/cl-l10n/cvsroot/cl-l10n/load-locale.lisp 2006/06/08 09:38:19 1.17 @@ -5,23 +5,69 @@ (defparameter *ignore-categories* (list "LC_CTYPE" "LC_COLLATE"))
+(defparameter *language->default-locale-name* (make-hash-table :test #'equal) + "This map specifies what is the default locale for locale specifications without a region (i.e. en_US for en)") + +(deftype locale-descriptor () + `(or locale string symbol)) + +(defun canonical-locale-name-from (locale) + (check-type locale locale-descriptor) + (if (typep locale 'locale) + (locale-name locale) + (let ((name locale)) + (when (and (not (null name)) + (symbolp name)) + (setf name (symbol-name name))) + (let* ((parts (split "_" name)) + (count (list-length parts)) + (first-length (length (first parts))) + (second-length (length (second parts)))) + (when (> count 2) + (error "Locale variants are not yet supported")) + (when (or (> first-length 3) + (< first-length 2) + (and (> count 1) + (or (> second-length 3) + (< second-length 2)))) + (error "~A is not a valid locale name (examples: en_GB, en_US, en)" locale)) + (let ((language (string-downcase (first parts))) + (region (when (> count 1) + (second parts)))) + (if (> count 1) + (concatenate 'string language "_" region) + (aif (gethash language *language->default-locale-name*) + it + (concatenate 'string language "_" (string-upcase language))))))))) + +;; set up the default region mappings while loading +(eval-when (:load-toplevel :execute) + (loop for (language locale) in + '((en "en_US")) do + (setf (gethash (string-downcase (symbol-name language)) *language->default-locale-name*) + (canonical-locale-name-from locale))) + (values)) + ;; Add a restart here? (defun locale (loc-name &key (use-cache t) (errorp t) (loader nil)) - "Find locale named by the string LOC-NAME. If USE-CACHE + "Find locale named by the specification LOC-NAME. If USE-CACHE is non-nil forcefully reload the locale from *locale-path* else the locale is first looked for in *locales*. If ERRORP is non-nil signal a warning rather than an error if the locale file cannot be found. If LOADER is non-nil skip everything and call loader with LOC-NAME." - (let ((name (aif (position #. loc-name) - (subseq loc-name 0 it) - loc-name))) - (acond ((and (not name) (not errorp)) nil) - ((and use-cache (get-locale name)) it) - (loader (setf (get-locale name) (funcall loader name))) - ((probe-file (merge-pathnames *locale-path* name)) - (setf (get-locale name) (load-locale name))) - (t (funcall (if errorp #'error #'warn) - "Can't find locale ~A." name))))) + (if (typep loc-name 'locale) + loc-name + (let ((name (canonical-locale-name-from + (aif (position #. loc-name) + (subseq loc-name 0 it) + loc-name)))) + (acond ((and (not name) (not errorp)) nil) + ((and use-cache (get-locale name)) it) + (loader (setf (get-locale name) (funcall loader name))) + ((probe-file (merge-pathnames *locale-path* name)) + (setf (get-locale name) (load-locale name))) + (t (funcall (if errorp #'error #'warn) + "Can't find locale ~A." name))))))
(defvar *locale-type* 'locale "The class of loaded locales.") @@ -29,18 +75,6 @@ (defvar *category-type* 'category "The class of loaded categories")
-(deftype locale-descriptor () - `(or locale string symbol)) - -(defun locale-des->locale (loc) - "Turns a locale descriptor(a string, symbol or locale) into an -actual locale object." - (check-type loc locale-descriptor) - (etypecase loc - (locale loc) - (string (locale loc)) - (symbol (locale (string loc))))) - (defun load-locale (name) (let ((path (merge-pathnames *locale-path* name)) (ef #+sbcl :iso-8859-1 @@ -360,16 +394,26 @@ (return-from next-header (trim line)))))
(defun set-locale (locale-des) - (setf *locale* (locale-des->locale locale-des))) + (setf *locale* (if (listp locale-des) + (loop for locale in locale-des + collect (locale locale)) + (locale locale-des)))) + +(defmacro with-locale (locale &body body) + `(let ((*locale* (locale ,locale))) + ,@body))
(defun load-default-locale () - (setf *locale* (get-default-locale))) + (set-locale (get-default-locale)))
(defun get-default-locale () - (or (locale (getenv "CL_LOCALE") :errorp nil) - (locale (getenv "LC_CTYPE") :errorp nil) - (locale (getenv "LANG") :errorp nil) - (locale "POSIX" :errorp nil))) + (macrolet ((try (name) + `(when-let (it (getenv ,name)) + (locale it :errorp nil)))) + (or (try "CL_LOCALE") + (try "LC_CTYPE") + (try "LANG") + (locale "POSIX" :errorp nil))))
(eval-when (:load-toplevel :execute) (load-default-locale)) --- /project/cl-l10n/cvsroot/cl-l10n/locale.lisp 2006/04/27 18:30:30 1.12 +++ /project/cl-l10n/cvsroot/cl-l10n/locale.lisp 2006/06/08 09:38:19 1.13 @@ -16,7 +16,14 @@ (merge-pathnames (make-pathname :directory '(:relative "locales")) (asdf:component-pathname (asdf:find-system :cl-l10n))))
-(defvar *locale* nil) +(defvar *locale* nil + "Either a locale or a list of locales in which case resources will be looked for in each locale in order.") + +(defun current-locale () + (declare (inline current-locale)) + (if (consp *locale*) + (car *locale*) + *locale*))
(defvar *locales* (make-hash-table :test #'equal) "Hash table containing all loaded locales keyed on name (eg. "af_ZA")") @@ -101,8 +108,8 @@ (defmacro defgetter (key cat &key (wrap '#'identity)) (let ((name (symb "LOCALE-" (substitute #- #_ (string-upcase key))))) `(progn - (defun ,name (&optional (locale *locale*)) - (let ((locale (locale-des->locale locale))) + (defun ,name (&optional (locale (current-locale))) + (let ((locale (locale locale))) (when locale (funcall ,wrap (locale-value locale ,cat ,key))))) (export ',name)))) --- /project/cl-l10n/cvsroot/cl-l10n/package.lisp 2006/04/27 18:30:30 1.8 +++ /project/cl-l10n/cvsroot/cl-l10n/package.lisp 2006/06/08 09:38:19 1.9 @@ -7,12 +7,16 @@ (:shadow cl:format cl:formatter) (:export #:locale-name #:category-name #:locale #:category #:locale-error #:get-category #:get-cat-val #:locale-value #:load-all-locales - #:*locale* #:*locale-path* #:*locales* #:load-default-locale + #:get-locale #:*locale-path* #:*locales* #:load-default-locale #:format-number #:print-number #:format-money #:print-money - #:format-time #:print-time #:add-resources #:bundle - #:add-resource #:gettext #:parse-number #:*float-digits* + #:format-time #:print-time #:add-resources + #:parse-number #:*float-digits* #:parse-time #:month #:day #:year #:hour #:minute #:second #:date-divider #:time-divider #:weekday #:noon-midn #:shadow-format - #:secondp #:am-pm #:zone #:parser-error #:set-locale)) + #:secondp #:am-pm #:zone #:parser-error #:set-locale + #:with-locale #:lookup-resource + #:lookup-resource-without-fallback #:localize + #:missing-resource #:defresources #:enable-sharpquote-reader + #:with-sharpquote-reader))
--- /project/cl-l10n/cvsroot/cl-l10n/parse-time.lisp 2006/04/27 18:30:30 1.3 +++ /project/cl-l10n/cvsroot/cl-l10n/parse-time.lisp 2006/06/08 09:38:19 1.4 @@ -600,7 +600,7 @@ ;; patterns have not been explicitly specified so we try ;; to match against locale a specific date pattern first. ;; eg. 03/04/2005 is 3rd April in UK but 4 March in US. - (dolist (pattern (parsers *locale*)) + (dolist (pattern (parsers (current-locale))) (let ((res (match-pattern pattern string-parts parts-length))) @@ -620,7 +620,7 @@ (default-hours nil) (default-day nil) (default-month nil) (default-year nil) (default-zone nil) (default-weekday nil) - (locale *locale*)) + (locale (current-locale))) "Tries very hard to make sense out of the argument time-string using locale and returns a single integer representing the universal time if successful. If not, it returns nil. If the :error-on-mismatch @@ -630,21 +630,21 @@ keywords can be given a numeric value or the keyword :current to set them to the current value. The default-default values are 00:00:00 on the current date, current time-zone." - (let* ((*error-on-mismatch* error-on-mismatch) - (*locale* (locale-des->locale locale)) - (string-parts (decompose-string time-string :start start :end end)) - (parts-length (length string-parts)) - (string-form (get-matching-pattern patterns string-parts parts-length))) - (if string-form - (let ((parsed-values (make-default-time default-seconds default-minutes - default-hours default-day - default-month default-year - default-zone default-weekday))) - (set-time-values string-form parsed-values) - (convert-to-unitime parsed-values)) - (if *error-on-mismatch* - (error 'parser-error :value time-string :reason "Not a recognized time/date format.") - nil)))) + (with-locale locale + (let* ((*error-on-mismatch* error-on-mismatch) + (string-parts (decompose-string time-string :start start :end end)) + (parts-length (length string-parts)) + (string-form (get-matching-pattern patterns string-parts parts-length))) + (if string-form + (let ((parsed-values (make-default-time default-seconds default-minutes + default-hours default-day + default-month default-year + default-zone default-weekday))) + (set-time-values string-form parsed-values) + (convert-to-unitime parsed-values)) + (if *error-on-mismatch* + (error 'parser-error :value time-string :reason "Not a recognized time/date format.") + nil)))))
; EOF --- /project/cl-l10n/cvsroot/cl-l10n/parsers.lisp 2005/05/18 15:34:08 1.4 +++ /project/cl-l10n/cvsroot/cl-l10n/parsers.lisp 2006/06/08 09:38:20 1.5 @@ -1,7 +1,7 @@ (in-package :cl-l10n)
-(defun parse-number (num &optional (locale *locale*)) - (let ((locale (locale-des->locale locale))) +(defun parse-number (num &optional (locale (current-locale))) + (let ((locale (locale locale))) (%parse-number (replace-dp (remove-ts num locale) locale))))
(defun remove-ts (num locale) --- /project/cl-l10n/cvsroot/cl-l10n/printers.lisp 2006/04/27 18:30:30 1.17 +++ /project/cl-l10n/cvsroot/cl-l10n/printers.lisp 2006/06/08 09:38:20 1.18 @@ -21,8 +21,8 @@ (princ "0" s)))))
(defun format-number (stream arg no-dp no-ts - &optional (locale *locale*)) - (let ((locale (locale-des->locale locale)) + &optional (locale (current-locale))) + (let ((locale (locale locale)) (float-part (float-part (coerce (abs arg) 'double-float)))) (cl:format stream (getf (printers locale) @@ -35,7 +35,7 @@ (values)))
(defun print-number (number &key (stream *standard-output*) - no-ts no-dp (locale *locale*)) + no-ts no-dp (locale (current-locale))) (format-number stream number no-dp no-ts locale) number)
@@ -60,8 +60,8 @@ :money-p-no-ts :money-p-ts)))
-(defun format-money (stream arg use-int-sym no-ts &optional (locale *locale*)) - (let* ((locale (locale-des->locale locale)) +(defun format-money (stream arg use-int-sym no-ts &optional (locale (current-locale))) + (let* ((locale (locale locale)) (frac-digits (max (if use-int-sym (locale-int-frac-digits locale) (locale-frac-digits locale)) @@ -85,7 +85,7 @@ (values))
(defun print-money (num &key (stream *standard-output*) use-int-sym no-ts - (locale *locale*)) + (locale (current-locale))) (format-money stream num use-int-sym no-ts locale) num)
@@ -119,15 +119,16 @@
(defun princ-pad-val (val stream &optional (pad "0") (size 2)) (declare (type stream stream) (optimize speed) - (type fixnum val)) + (type fixnum val size)) (assert (not (minusp val)) (val) "Value ~A cannot be smaller than 0." val) (cond ((zerop val) (dotimes (x (1- size)) (princ pad stream)) (princ 0 stream)) (t - (loop for x = (* val 10) then (* x 10) - until (>= x (expt 10 size)) do + (loop with stop-value = (expt 10 size) + for x integer = (* val 10) then (* x 10) + until (>= x stop-value) do (princ pad stream)) (princ val stream))))
@@ -316,8 +317,8 @@
(defvar *time-zone*)
-(defun format-time (stream ut show-date show-time &optional (locale *locale*) fmt time-zone) - (let ((locale (locale-des->locale (or locale *locale*))) +(defun format-time (stream ut show-date show-time &optional (locale (current-locale)) fmt time-zone) + (let ((locale (locale locale)) (*time-zone* (or time-zone (nth-value 8 (decode-universal-time ut))))) (print-time-string (or fmt (get-time-fmt-string locale show-date show-time)) @@ -348,7 +349,7 @@ (princ x stream)))))))
(defun print-time (ut &key show-date show-time (stream *standard-output*) - (locale *locale*) fmt time-zone) + (locale (current-locale)) fmt time-zone) (format-time stream ut show-date show-time locale fmt time-zone) ut)
@@ -386,7 +387,7 @@ string))
(defun really-parse-fmt-string (string) - (declare (optimize speed) (type string string)) + (declare (optimize speed) (type simple-string string)) (with-output-to-string (fmt-string) (loop for char across string with tilde = nil do