1 ;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Package: INTL -*-
4 ;;; Copyright 1999-2010 Paul Foley (mycroft@actrix.gen.nz)
6 ;;; Permission is hereby granted, free of charge, to any person obtaining
7 ;;; a copy of this Software to deal in the Software without restriction,
8 ;;; including without limitation the rights to use, copy, modify, merge,
9 ;;; publish, distribute, sublicense, and/or sell copies of the Software,
10 ;;; and to permit persons to whom the Software is furnished to do so,
11 ;;; provided that the above copyright notice and this permission notice
12 ;;; are included in all copies or substantial portions of the Software.
14 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
15 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
18 ;;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 ;;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
20 ;;; OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
21 ;;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 ;;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 ;;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
24 ;;; USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
28 (eval-when (:compile-toplevel
:execute
)
29 (defparameter intl
::*default-domain
* "maxima")
30 (unless (and (fboundp 'intl
:read-translatable-string
)
31 (eq (get-macro-character #\_
)
32 (fdefinition 'intl
:read-translatable-string
)))
33 (set-macro-character #\_
(lambda (stream char
)
34 (declare (ignore char
))
35 (case (peek-char nil stream nil nil t
)
37 (#\N
(read-char stream t nil t
) (values))
40 (defvar *locale-directories
* '(#p
"/usr/share/locale/"))
43 (defvar *default-domain
* "maxima"
44 _N
"The message-lookup domain used by INTL:GETTEXT and INTL:NGETTEXT.
45 Use (INTL:TEXTDOMAIN \"whatever\") in each source file to set this.")
46 (defvar *loaded-domains
* (make-hash-table :test
'equal
))
47 (defvar *locale-aliases
* (make-hash-table :test
'equal
))
49 (defstruct domain-entry
50 (domain "" :type simple-string
)
51 (locale "" :type simple-string
)
52 (file #p
"" :type pathname
)
53 (plurals nil
:type
(or null function
))
54 (hash (make-hash-table :test
'equal
) :type hash-table
)
56 (readfn #'identity
:type function
))
58 (declaim (ftype (function (stream) (unsigned-byte 32)) read-lelong
))
59 (defun read-lelong (stream)
60 (declare (optimize (speed 3) (space 2)
61 #+CMU
(ext:inhibit-warnings
3))) ;quiet about boxing retn
62 (+ (the (unsigned-byte 8) (read-byte stream
))
63 (ash (the (unsigned-byte 8) (read-byte stream
)) 8)
64 (ash (the (unsigned-byte 8) (read-byte stream
)) 16)
65 (ash (the (unsigned-byte 8) (read-byte stream
)) 24)))
67 (declaim (ftype (function (stream) (unsigned-byte 32)) read-belong
))
68 (defun read-belong (stream)
69 (declare (optimize (speed 3) (space 2)
70 #+CMU
(ext:inhibit-warnings
3))) ;quiet about boxing retn
71 (+ (ash (the (unsigned-byte 8) (read-byte stream
)) 24)
72 (ash (the (unsigned-byte 8) (read-byte stream
)) 16)
73 (ash (the (unsigned-byte 8) (read-byte stream
)) 8)
74 (the (unsigned-byte 8) (read-byte stream
))))
76 (defun locate-domain-file (domain locale locale-dir
)
77 (flet ((path (locale base
)
78 (merge-pathnames (make-pathname :directory
(list :relative locale
80 :name domain
:type
"mo")
82 (let ((locale (or (gethash locale
*locale-aliases
*) locale
)))
83 (dolist (base (if (listp locale-dir
) locale-dir
(list locale-dir
)))
85 (or (probe-file (path locale base
))
86 (let ((dot (position #\. locale
)))
87 (and dot
(probe-file (path (subseq locale
0 dot
) base
))))
88 (let ((at (position #\
@ locale
)))
89 (and at
(probe-file (path (subseq locale
0 at
) base
))))
90 (let ((us (position #\_ locale
)))
91 (and us
(probe-file (path (subseq locale
0 us
) base
)))))))
92 (when probe
(return probe
)))))))
94 (defun find-encoding (domain)
95 (when (null (domain-entry-encoding domain
))
96 (setf (domain-entry-encoding domain
) :iso-8859-1
)
97 (let* ((header (domain-lookup "" domain
))
98 (ctype (search "Content-Type: " header
))
99 (eoln (and ctype
(position #\Newline header
:start ctype
)))
100 (charset (and ctype
(search "; charset=" header
101 :start2 ctype
:end2 eoln
))))
104 (loop for i upfrom charset below eoln as c
= (char header i
)
105 while
(or (alphanumericp c
) (eql c
#\-
))
106 finally
(setf (domain-entry-encoding domain
)
107 (intern (nstring-upcase (subseq header charset i
))
111 (defun parse-plurals (domain)
112 (let* ((header (domain-lookup "" domain
))
113 (plurals (search "Plural-Forms: " header
))
114 (default (lambda (n) (if (= n
1) 0 1))))
116 (> (length header
) (+ plurals
36))
117 (string= header
"nplurals="
118 :start1
(+ plurals
14) :end1
(+ plurals
23)))
120 (parse-integer header
:start
(+ plurals
23) :junk-allowed t
))
121 (point (+ (position #\
; header :start (+ plurals 23)) 2)))
122 (if (and (> (length header
) (+ point
10))
123 (string= header
"plural=" :start1 point
:end1
(+ point
7)))
124 (values (parse-expr header
(+ point
7)) nplurals
)
126 (values default
2))))
128 (defun parse-expr (string pos
)
130 (loop while
(member (char string pos
) '(#\Space
#\Tab
#\Newline
))
132 (case (char string
(1- (incf pos
)))
146 (#\|
(if (char= (char string pos
) #\|
)
147 (progn (incf pos
) 'cor
)
149 (#\
& (if (char= (char string pos
) #\
&)
150 (progn (incf pos
) 'cand
)
152 (#\
= (if (char= (char string pos
) #\
=)
153 (progn (incf pos
) 'cmp
=)
154 (error _
"Encountered illegal token: =")))
155 (#\
! (if (char= (char string pos
) #\
=)
156 (progn (incf pos
) 'cmp
/=)
158 (#\
< (case (char string pos
)
159 (#\
= (incf pos
) 'cmp
<=)
160 (#\
< (incf pos
) 'shl
)
162 (#\
> (case (char string pos
)
163 (#\
= (incf pos
) 'cmp
>=)
164 (#\
> (incf pos
) 'shr
)
166 (otherwise (let ((n (digit-char-p (char string
(1- pos
)))))
168 (loop for nx
= (digit-char-p (char string pos
))
170 do
(setq n
(+ (* n
10) nx
)) (incf pos
)
172 (error _
"Encountered illegal token: ~C"
173 (char string
(1- pos
))))))))
174 (conditional (tok &aux tree
)
175 (multiple-value-setq (tree tok
) (logical-or tok
))
177 (multiple-value-bind (right next
) (logical-or (next))
178 (unless (eql next
'then
)
179 (error _
"Expected : in ?: construct"))
180 (multiple-value-bind (else next
) (conditional (next))
181 (setq tree
(list tok
(list 'zerop tree
) else right
)
184 (logical-or (tok &aux tree
)
185 (multiple-value-setq (tree tok
) (logical-and tok
))
186 (loop while
(eql tok
'cor
) do
187 (multiple-value-bind (right next
) (logical-and (next))
188 (setq tree
(list tok tree right
)
191 (logical-and (tok &aux tree
)
192 (multiple-value-setq (tree tok
) (inclusive-or tok
))
193 (loop while
(eql tok
'cand
) do
194 (multiple-value-bind (right next
) (inclusive-or (next))
195 (setq tree
(list tok tree right
)
198 (inclusive-or (tok &aux tree
)
199 (multiple-value-setq (tree tok
) (exclusive-or tok
))
200 (loop while
(eql tok
'logior
) do
201 (multiple-value-bind (right next
) (exclusive-or (next))
202 (setq tree
(list tok tree right
)
205 (exclusive-or (tok &aux tree
)
206 (multiple-value-setq (tree tok
) (bitwise-and tok
))
207 (loop while
(eql tok
'logxor
) do
208 (multiple-value-bind (right next
) (bitwise-and (next))
209 (setq tree
(list tok tree right
)
212 (bitwise-and (tok &aux tree
)
213 (multiple-value-setq (tree tok
) (equality tok
))
214 (loop while
(eql tok
'logand
) do
215 (multiple-value-bind (right next
) (equality (next))
216 (setq tree
(list tok tree right
)
219 (equality (tok &aux tree
)
220 (multiple-value-setq (tree tok
) (relational tok
))
221 (loop while
(member tok
'(cmp= cmp
/=)) do
222 (multiple-value-bind (right next
) (relational (next))
223 (setq tree
(list tok tree right
)
226 (relational (tok &aux tree
)
227 (multiple-value-setq (tree tok
) (shift tok
))
228 (loop while
(member tok
'(cmp< cmp
> cmp
<= cmp
>=)) do
229 (multiple-value-bind (right next
) (shift (next))
230 (setq tree
(list tok tree right
)
233 (shift (tok &aux tree
)
234 (multiple-value-setq (tree tok
) (additive tok
))
235 (loop while
(member tok
'(shl shr
)) do
236 (multiple-value-bind (right next
) (additive (next))
237 (setq tree
(list tok tree right
)
240 (additive (tok &aux tree
)
241 (multiple-value-setq (tree tok
) (multiplicative tok
))
242 (loop while
(member tok
'(add sub
)) do
243 (multiple-value-bind (right next
) (multiplicative (next))
244 (setq tree
(list tok tree right
)
247 (multiplicative (tok &aux tree
)
248 (multiple-value-setq (tree tok
) (unary tok
))
249 (loop while
(member tok
'(mul floor mod
)) do
250 (multiple-value-bind (right next
) (unary (next))
251 (setq tree
(list tok tree right
)
254 (unary (tok &aux tree
)
255 (cond ((eq tok
'lpar
)
256 (multiple-value-setq (tree tok
) (conditional (next)))
257 (unless (eq tok
'rpar
)
258 (error _
"Expected close-paren."))
259 (values tree
(next)))
267 (multiple-value-setq (tree tok
) (unary (next)))
268 (values (list '- tree
) tok
))
270 (multiple-value-setq (tree tok
) (unary (next)))
271 (values (list 'lognot32 tree
) tok
))
273 (multiple-value-setq (tree tok
) (unary (next)))
274 (values (list 'cnot tree
) tok
))
276 (error _
"Unexpected token: ~S." tok
)))))
277 (multiple-value-bind (tree end
) (conditional (next))
278 (unless (eq end
'end
)
279 (error _
"Expecting end of expression. ~S." end
))
280 (let ((*compile-print
* nil
))
283 (declare (type (unsigned-byte 32) n
)
284 (optimize (space 3)))
285 (flet ((add (a b
) (ldb (byte 32 0) (+ a b
)))
286 (sub (a b
) (ldb (byte 32 0) (- a b
)))
287 (mul (a b
) (ldb (byte 32 0) (* a b
)))
288 (shl (a b
) (ldb (byte 32 0) (ash a b
)))
289 (shr (a b
) (ash a
(- b
)))
290 (cmp= (a b
) (if (= a b
) 1 0))
291 (cmp/= (a b
) (if (/= a b
) 1 0))
292 (cmp< (a b
) (if (< a b
) 1 0))
293 (cmp<= (a b
) (if (<= a b
) 1 0))
294 (cmp> (a b
) (if (> a b
) 1 0))
295 (cmp>= (a b
) (if (>= a b
) 1 0))
296 (cand (a b
) (if (or (zerop a
) (zerop b
)) 0 1))
297 (cor (a b
) (if (and (zerop a
) (zerop b
)) 0 1))
298 (cnot (a) (if a
0 1))
299 (lognot32 (a) (ldb (byte 32 0) (lognot a
))))
300 (declare (ignorable #'add
#'sub
#'mul
#'shr
#'shl
302 #'cmp
< #'cmp
<= #'cmp
> #'cmp
>=
303 #'cand
#'cor
#'cnot
#'lognot32
))
306 (defun load-domain (domain locale
&optional
(locale-dir *locale-directories
*))
307 (let ((file (locate-domain-file domain locale locale-dir
))
308 (read #'read-lelong
))
309 (unless file
(return-from load-domain nil
))
310 (with-open-file (stream file
:direction
:input
:if-does-not-exist nil
311 :element-type
'(unsigned-byte 8))
312 (unless stream
(return-from load-domain nil
))
313 (let ((magic (read-lelong stream
)))
314 (cond ((= magic
#x950412de
) (setq read
#'read-lelong
))
315 ((= magic
#xde120495
) (setq read
#'read-belong
))
317 (error _
"Bad magic number in \"~A.mo\"." domain
))))
318 (let ((version (funcall read stream
))
319 (messages (funcall read stream
))
320 (master (funcall read stream
))
321 (translation (funcall read stream
))
322 (entry (make-domain-entry)))
323 (declare (ignore version
))
324 (setf (domain-entry-readfn entry
) read
)
325 (setf (domain-entry-domain entry
) domain
)
326 (setf (domain-entry-locale entry
) locale
)
327 (setf (domain-entry-file entry
) file
)
328 (dotimes (msg messages
)
329 (file-position stream
(+ master
(* 8 msg
)))
330 (let ((length (funcall read stream
))
331 (start (funcall read stream
)))
332 (setf (gethash length
(domain-entry-hash entry
))
333 (acons start
(+ translation
(* 8 msg
))
334 (gethash length
(domain-entry-hash entry
))))))
335 (setf (gethash domain
*loaded-domains
*) entry
)
336 (find-encoding entry
)))))
338 (defun find-domain (domain locale
&optional
(locale-dir *locale-directories
*))
339 (let ((found (gethash domain
*loaded-domains
*)))
340 (if (and found
(string= (domain-entry-locale found
) locale
))
342 (load-domain domain locale locale-dir
))))
344 (declaim (inline string-to-octets
))
345 (defun string-to-octets (string encoding
)
346 (declare (ignorable encoding
))
348 (ext:string-to-octets string
:external-format encoding
)
350 (ext:make-bytes-from-string string encoding
)
352 (excl:string-to-octets string
:external-format encoding
:null-terminate nil
)
354 (sb-ext:string-to-octets string
:external-format encoding
357 (ext:convert-string-to-bytes string
(ext:make-encoding
:charset
(symbol-name encoding
)))
358 ;;@@ add other implementations
359 #-
(or (and CMU Unicode
) Allegro SBCL CLISP scl
#|others|
#)
360 (map-into (make-array (length string
) :element-type
'(unsigned-byte 8))
363 (declaim (inline octets-to-string
))
364 (defun octets-to-string (octets encoding
)
365 (declare (ignorable encoding
))
367 (ext:octets-to-string octets
:external-format encoding
)
369 (ext:make-string-from-bytes octets encoding
)
371 (excl:octets-to-string octets
:external-format encoding
:end
(length octets
))
373 (sb-ext:octets-to-string octets
:external-format encoding
)
374 #+CLISP
;;@@ Not sure if encoding keyword is OK here
375 (ext:convert-string-from-bytes octets
(ext:make-encoding
:charset
(symbol-name encoding
)))
376 ;;@@ add other implementations
377 #-
(or (and CMU Unicode
) Allegro SBCL CLISP scl
#|others|
#)
378 (map-into (make-string (length octets
)) #'code-char octets
))
380 (defun octets= (a b
&key
(start1 0) (end1 (length a
))
381 (start2 0) (end2 (length b
)))
382 (declare (type (simple-array (unsigned-byte 8) (*)) a b
)
383 (type (integer 0 #.array-dimension-limit
) start1 end1 start2 end2
)
384 (optimize (speed 3) (space 2) #-gcl
(debug 0)))
385 (when (and (< start1 end1
)
388 (unless (= (aref a start1
) (aref b start2
)) (return nil
))
389 (when (or (= (incf start1
) end1
) (= (incf start2
) end2
)) (return t
)))))
391 (defun search-domain (octets domain pos
)
392 (declare (type (simple-array (unsigned-byte 8) (*)) octets
)
393 (type domain-entry domain
)
395 (optimize (speed 3) (space 2) #-gcl
(debug 0)
396 #+CMU
(ext:inhibit-warnings
3))) ; quiet about boxing
398 (let ((temp (make-array 120 :element-type
'(unsigned-byte 8)))
399 (length (length octets
)))
400 (with-open-file (stream (domain-entry-file domain
)
402 :element-type
'(unsigned-byte 8))
404 (file-position stream
(car entry
))
406 (end (read-sequence temp stream
407 :end
(min 120 length
))))
408 (declare (type (integer 0 #.array-dimension-limit
) off end
))
409 (loop while
(octets= octets temp
411 :end1
(min (+ off
120) length
)
416 (setf end
(read-sequence temp stream
417 :end
(min 120 (- length off
))))))
419 (file-position stream
(cdr entry
))
420 (let* ((len (funcall (domain-entry-readfn domain
) stream
))
421 (off (funcall (domain-entry-readfn domain
) stream
))
422 (tmp (make-array len
:element-type
'(unsigned-byte 8))))
423 (file-position stream off
)
424 (read-sequence tmp stream
)
425 (return (values tmp entry
))))))))))
427 (defun domain-lookup (string domain
)
428 (declare (type string string
) (type domain-entry domain
)
429 (optimize (speed 3) (space 2)))
430 (or (if (null (domain-entry-encoding domain
)) string
)
431 (gethash string
(domain-entry-hash domain
))
432 (let* ((octets (string-to-octets string
433 (domain-entry-encoding domain
)))
434 (length (length octets
))
435 (pos (gethash length
(domain-entry-hash domain
))))
436 (declare (type (simple-array (unsigned-byte 8) (*)) octets
))
437 (multiple-value-bind (tmp entry
) (search-domain octets domain pos
)
438 (declare (type (or null
(simple-array (unsigned-byte 8) (*))) tmp
))
440 (let ((temp (delete entry pos
:test
#'eq
)))
442 (setf (gethash length
(domain-entry-hash domain
)) temp
)
443 (remhash length
(domain-entry-hash domain
))))
444 (setf (gethash (copy-seq string
) (domain-entry-hash domain
))
445 (octets-to-string tmp
(domain-entry-encoding domain
))))))))
447 (defun domain-lookup-plural (singular plural domain
)
448 (declare (type string singular plural
) (type domain-entry domain
)
449 (optimize (speed 3) (space 2)))
450 (or (if (null (domain-entry-encoding domain
)) nil
)
451 (gethash (cons singular plural
) (domain-entry-hash domain
))
452 (let* ((octets (let* ((a (string-to-octets singular
453 (domain-entry-encoding domain
)))
454 (b (string-to-octets plural
455 (domain-entry-encoding domain
)))
456 (c (make-array (+ (length a
) (length b
) 1)
457 :element-type
'(unsigned-byte 8))))
458 (declare (type (simple-array (unsigned-byte 8) (*))
461 (setf (aref c
(length a
)) 0)
462 (replace c b
:start1
(+ (length a
) 1))
464 (length (length octets
))
465 (pos (gethash length
(domain-entry-hash domain
))))
466 (declare (type (simple-array (unsigned-byte 8) (*)) octets
)
468 (multiple-value-bind (tmp entry
) (search-domain octets domain pos
)
469 (declare (type (or null
(simple-array (unsigned-byte 8) (*))) tmp
))
472 (setf (gethash (cons (copy-seq singular
) (copy-seq plural
))
473 (domain-entry-hash domain
))
474 (loop for i
= 0 then
(1+ j
)
475 as j
= (position 0 tmp
:start i
)
476 collect
(octets-to-string (subseq tmp i j
)
477 (domain-entry-encoding domain
))
479 (let ((temp (delete entry pos
:test
#'eq
)))
481 (setf (gethash length
(domain-entry-hash domain
)) temp
)
482 (remhash length
(domain-entry-hash domain
))))
483 (when (null (domain-entry-plurals domain
))
484 (setf (domain-entry-plurals domain
)
485 (parse-plurals domain
)))))))))
487 (declaim (inline getenv
)
488 (ftype (function (string) (or null string
)) getenv
))
490 (let ((val #+CMU
(cdr (assoc (intern var
"KEYWORD") ext
:*environment-list
*))
491 #+scl
(cdr (assoc var ext
:*environment-list
* :test
'string
=))
492 #+SBCL
(sb-ext:posix-getenv var
)
493 #+Allegro
(system:getenv var
)
494 #+LispWorks
(hcl:getenv var
)
495 #+clisp
(ext:getenv var
)
496 #+(or openmcl mcl
) (ccl::getenv var
)
497 #+(or gcl ecl
) (si::getenv var
)))
498 (if (equal val
"") nil val
)))
500 (defun setlocale (&optional locale
)
501 (setf *locale
* (or locale
504 (getenv "LC_MESSAGES")
508 (defmacro textdomain
(domain)
509 `(eval-when (:compile-toplevel
:execute
)
510 (setf *default-domain
* ,domain
)))
512 (defmacro gettext
(string)
513 _N
"Look up STRING in the current message domain and return its translation."
514 `(dgettext ,*default-domain
* ,string
))
516 (defmacro ngettext
(singular plural n
)
517 _N
"Look up the singular or plural form of a message in the current domain."
518 `(dngettext ,*default-domain
* ,singular
,plural
,n
))
520 (declaim (inline dgettext
))
521 (defun dgettext (domain string
)
522 _N
"Look up STRING in the specified message domain and return its translation."
523 (declare (optimize (speed 3) (space 2)))
524 (let ((domain (and domain
(find-domain domain
*locale
*))))
525 (or (and domain
(domain-lookup string domain
)) string
)))
527 (defun dngettext (domain singular plural n
)
528 _N
"Look up the singular or plural form of a message in the specified domain."
529 (declare (type integer n
)
530 (optimize (speed 3) (space 2)))
531 (let* ((domain (and domain
(find-domain domain
*locale
*)))
532 (list (and domain
(domain-lookup-plural singular plural domain
))))
535 (funcall (the function
(domain-entry-plurals domain
)) n
))
537 (if (= n
1) singular plural
))))
539 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
542 (defvar *translator-comment
* nil
)
545 (defvar *translations
* (make-hash-table :test
'equal
))
548 (defun note-translatable (domain string
&optional plural
)
550 (let* ((hash (or (gethash domain
*translations
*)
551 (setf (gethash domain
*translations
*)
552 (make-hash-table :test
'equal
))))
553 (key (if plural
(cons string plural
) string
))
554 (val (or (gethash key hash
) (cons nil nil
))))
555 (pushnew *translator-comment
* (car val
) :test
#'equal
)
557 (pushnew *compile-file-pathname
* (cdr val
) :test
#'equal
)
558 (setf (gethash key hash
) val
)))
559 (setq *translator-comment
* nil
))
561 ;; GCL has define-compiler-macro, but it doesn't handle the case where
562 ;; the form is returned. Hence, disable these. These were only
563 ;; needed to note the translatable strings anyway, and maxima does
564 ;; that in a different way.
566 (define-compiler-macro dgettext
(&whole form domain string
)
568 (when (and (stringp domain
) (stringp string
))
569 (note-translatable domain string
))
573 (define-compiler-macro dngettext
(&whole form domain singular plural n
)
576 (when (and (stringp domain
) (stringp singular
) (stringp plural
))
577 (note-translatable domain singular plural
))
580 (defun read-translatable-string (stream char
)
581 (declare (ignore char
))
582 (case (peek-char nil stream nil nil t
)
583 (#\" (let ((*read-suppress
* nil
)
584 (string (read stream t nil t
)))
586 (#\N
(read-char stream t nil t
)
587 (let ((*read-suppress
* nil
)
588 (string (read stream t nil t
)))
590 (note-translatable *default-domain
* string
)
592 (#\
@ (error _
"_@ is a reserved reader macro prefix."))
594 (let ((fn (get-macro-character #\_ nil
)))
595 (if fn
(funcall fn stream
#\_
) '_
)))))
598 (defun read-comment (stream char
)
599 (declare (optimize (speed 0) (space 3) #-gcl
(debug 0))
604 (char (read-char stream nil nil t
) (read-char stream nil nil t
)))
605 ((or (not char
) (char= char
#\Newline
))
606 (when text
(setq *translator-comment
* (copy-seq text
))))
607 (cond ((and (= state
0) (char= char
#\Space
)) (setq state
1))
608 ((and (= state
0) (char= char
#\T
)) (setq state
1 index
1))
609 ((and (= state
0) (char/= char
#\
;)) (setq state 2))
610 ((and (= state
1) (= index
0) (char= char
#\Space
)) #|ignore|
#)
612 (if (char= char
(char "TRANSLATORS: " index
))
613 (when (= (incf index
) 13)
618 (setq text
(make-array 50 :element-type
'character
619 :adjustable t
:fill-pointer
0)))
620 (vector-push-extend char text
))))
624 (defun read-nested-comment (stream subchar arg
)
625 (declare (ignore subchar arg
)
626 (optimize (speed 0) (space 3) #-gcl
(debug 0)))
631 (prev (read-char stream t nil t
) char
)
632 (char (read-char stream t nil t
) (read-char stream t nil t
)))
634 (cond ((and (char= prev
#\|
) (char= char
#\
#))
635 (when (zerop (decf level
))
637 (setq *translator-comment
*
638 (string-right-trim '(#\Space
#\Newline
) text
)))
640 ((and (char= prev
#\
#) (char= char
#\|
))
643 ((and (= state
0) (char= prev
#\Space
)) (setq state
1))
644 ((and (= state
0) (char= prev
#\T
))
645 (setq state
1 index
1))
646 ((= state
0) (setq state
2))
647 ((and (= state
1) (= index
0) (char= prev
#\Space
)) #| ignore |
#)
649 (if (char= prev
(char "TRANSLATORS: " index
))
650 (when (= (incf index
) 13)
655 (setq text
(make-array 50 :element-type
'character
656 :adjustable t
:fill-pointer
0)))
657 (vector-push-extend prev text
))))
661 (set-macro-character #\_
#'read-translatable-string t
)
663 (set-macro-character #\
; #'read-comment)
665 (set-dispatch-macro-character #\
# #\|
#'read-nested-comment
)
670 (defun dump-pot-files (&key copyright
)
671 (declare (optimize (speed 0) (space 3) #-gcl
(debug 1)))
672 (labels ((b (key data
)
673 (format t
"~@[~{~&#. ~A~}~%~]" (delete nil
(car data
)))
674 (format t
"~@[~&~<#: ~@;~@{~A~^ ~}~:@>~%~]"
675 (delete nil
(cdr data
)))
677 (format t
"~&msgid ") (str (car key
) 6 0)
678 (format t
"~&msgid_plural ") (str (cdr key
) 13 0)
679 (format t
"~&msgstr[0] \"\"~2%"))
681 (format t
"~&msgid ") (str key
6 0)
682 (format t
"~&msgstr \"\"~2%"))))
683 (str (string col start
)
684 (when (and (plusp col
) (> (length string
) (- 76 col
)))
686 (let ((nl (position #\Newline string
:start start
)))
687 (cond ((and nl
(< (- nl start
) 76))
689 (wstr string start nl
)
691 (str string
0 (1+ nl
)))
692 ((< (- (length string
) start
) 76)
694 (wstr string start
(length string
))
697 (let* ((a (+ start
1))
699 (b1 (position #\Space string
:start a
:end b
701 (b2 (position-if (lambda (x)
702 (position x
";:,?!)]}"))
703 string
:start a
:end b
705 (b3 (position-if (lambda (x)
707 string
:start a
:end b
709 (b4 (position-if #'digit-char-p
710 string
:start a
:end b
712 (b5 (position-if #'alpha-char-p
713 string
:start a
:end b
715 (g1 (if b1
(* (- b b1
) (- b b1
) .03) 10000))
716 (g2 (if b2
(* (- b b2
) (- b b2
) .20) 10000))
717 (g3 (if b3
(* (- b b3
) (- b b3
) .97) 10000))
718 (g4 (if b4
(* (- b b4
) (- b b4
) 1.3) 10000))
719 (g5 (if b5
(* (- b b5
) (- b b5
) 2.0) 10000))
720 (g (min g1 g2 g3 g4 g5
))
721 (end (1+ (cond ((> g
750) b
)
729 (format t
"~&Splitting ~S:~%"
730 (subseq string start b
))
731 (format t
"~{~& b~D=~D; goodness=~F~}~%"
732 (list 1 b1 g1
2 b2 g2
3 b3 g3
4 b4 g4
5 b5 g5
734 (format t
"~& best=~F == ~D~%" g end
)
735 (format t
"~& Part1=~S~% Part2=~S~%"
736 (subseq string start end
)
737 (subseq string end b
)))
739 (wstr string start end
)
740 (write-char #\") (terpri)
741 (str string
0 end
))))))
742 (wstr (string start end
)
743 (loop while
(< start end
) do
744 (let ((i (position-if (lambda (x)
745 (or (char= x
#\") (char= x
#\\)))
746 string
:start start
:end end
)))
747 (write-string string nil
:start start
:end
(or i end
))
748 (when i
(write-char #\\ nil
) (write-char (char string i
) nil
))
749 (setq start
(if i
(1+ i
) end
)))))
751 (format t
"~&#@ ~A~2%" domain
)
752 (format t
"~&# SOME DESCRIPTIVE TITLE~%")
753 (format t
"~@[~&# Copyright (C) YEAR ~A~%~]" copyright
)
754 (format t
"~&# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR~%")
755 (format t
"~&#~%#, fuzzy~%msgid \"\"~%msgstr \"\"~%")
756 (format t
"~&\"Project-Id-Version: PACKAGE VERSION\\n\"~%")
757 (format t
"~&\"Report-Msgid-Bugs-To: \\n\"~%")
758 (format t
"~&\"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\\n\"~%")
759 (format t
"~&\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"~%")
760 (format t
"~&\"Language-Team: LANGUAGE <LL@li.org>\\n\"~%")
761 (format t
"~&\"MIME-Version: 1.0\\n\"~%")
762 (format t
"~&\"Content-Type: text/plain; charset=UTF-8\\n\"~%")
763 (format t
"~&\"Content-Transfer-Encoding: 8bit\\n\"~2%")
765 (maphash #'a
*translations
*)
767 (clrhash *translations
*))
772 (eval-when (:compile-toplevel
:execute
)
773 (setq *default-domain
* "maxima")
774 (unless (and (fboundp 'intl
:read-translatable-string
)
775 (eq (get-macro-character #\_
)
776 (fdefinition 'intl
:read-translatable-string
)))
777 (set-syntax-from-char #\_
#\_
)))