[cl-l10n-cvs] CVS cl-l10n

alendvai alendvai at common-lisp.net
Thu Jun 8 09:38:20 UTC 2006


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 at 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 , at 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 , at 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)))
+    , at 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




More information about the Cl-l10n-cvs mailing list