Gauche:翻訳支援スクリプト

Gauche:翻訳支援スクリプト

Shiro (2003/02/13 04:02:44 PST): Practical Schemeに置いてる翻訳文書 を作成する時に使っているスクリプトです。 たいしたもんじゃないですが、使い捨てフィルタスクリプトの例になる かとも思うので貼りつけておきます。 最近(「ものつくりのセンス」以降かな)の翻訳記事はこのフィルタを通しています。

私のHTML文書の翻訳の方法は、対象文書のソースに対して、 パラグラフ毎に原文の下に日本語を書き加えてゆくというものです。 パラグラフで並置しておくのがチェックしやすいので。 その際に、"@en" と "@jp" で日本語パラグラフと英語パラグラフを区切っておけば、 このフィルタを通せば英語部分がHTMLのコメントになるという仕掛けです。 あと、訳註をつける機能もついています。というか、それだけのスクリプトです。

#!/usr/bin/gosh

;; HTMLドキュメント翻訳のためのフィルタ
;; $Id: transfmt,v 1.2 2003/02/11 12:08:04 shirok Exp $
;;
;; 言語スイッチ
;;
;;    @en
;;    English content
;;    @jp
;;    日本語コンテンツ
;;    @common
;;    Common content (e.g. program list)
;;
;;   各言語スイッチは行頭になければならず、次の言語スイッチが出て来るまで
;;   有効になる。
;;   フィルタを通すと、English contentの部分がHTMLでコメントアウトされる。
;;
;; 訳註
;;
;;    ほげほげ@jnote{ラベル}ふげふげ
;;
;;    @jnote ラベル
;;    訳註内容
;;    @end jnote
;;
;;    ...
;;
;;    <hr>
;;    <h3>訳註</h3>
;;    @jnotes
;;
;;   @jnote{ラベル} の部分は [訳註N] に展開され、訳註内容へのリンクが
;;   張られる。 Nは自動的に割り当てられる。 訳註はまとめて@jnotesの
;;   ある場所に展開される。
;;
;; 起動
;;
;;   transfmt [-o output] input-file

(use srfi-13)
(use file.filter)
(use gauche.parseopt)

(define *jlabels* '())
(define *jnotes* '())

(define (process-block generator consumer)
  (define (in-en line)
    (cond ((eof-object? line))
          ((string=? "@en" line)     (in-en (generator)))
          ((string=? "@jp" line)     (consumer " -->")  (in-jp (generator)))
          ((string=? "@common" line) (consumer " -->")  (in-jp (generator)))
          ((string=? "@jnotes" line)
           (error "@jnotes appeared in @en block"))
          (else (consumer (regexp-replace-all #/-+/ line "-"))
                (in-en (generator)))))
  (define (in-jp line)
     (cond ((eof-object? line))
          ((string=? "@en" line)     (consumer "<!-- ") (in-en (generator)))
          ((string=? "@jp" line)     (in-jp (generator)))
          ((string=? "@jnotes" line) (process-jnotes))
          ((string-prefix? "@jnote" line) (in-jnote line))
          ((string=? "@common" line) (in-jp (generator)))
          (else (consumer line) (in-jp (generator)))))
  (define (in-jnote line)
    (rxmatch-let (rxmatch #/^@jnote\s*(\w+)/ line)
        (#f label)
      (let loop ((line (generator))
                 (content '()))
        (cond ((eof-object? line)    (error "unterminated @jnote"))
              ((string-prefix? "@" line)
               (if (rxmatch #/^@end\s+jnote\s*$/ line)
                   (begin (push! *jnotes* (cons label (reverse content)))
                          (in-jp (generator)))
                   (errorf "~a appeared without @end jnote" line)))
              (else (loop (generator) (cons line content)))))))
  (define (process-jnotes)
    (consumer "<dl>")
    (for-each (lambda (labentry)
                (let ((entry (assoc (car labentry) *jnotes*)))
                  (unless entry
                    (error "No @jnote for label" (car labentry)))
                  (consumer #`"<dt> <a name=\",(car labentry)\" href=\"#,(car labentry)_anchor\">訳註,(cdr labentry)</a>:")
                  (consumer "<dd>")
                  (consumer (string-join (cdr entry) "\n"))))
              (sort *jlabels*
                    (lambda (a b) (< (cdr a) (cdr b)))))
    (consumer "</dl>")
    (in-jp (generator)))
  (in-jp (generator)))

(define (process-line line)
  (regexp-replace-all
   #/@jnote{(\w+)}/
   line
   (lambda (m)
     (let ((label (m 1))
           (nlab  (+ (length *jlabels*) 1)))
       (when (assoc label *jlabels*)
         (error "duplicate label" label))
       (push! *jlabels* (cons label nlab))
       #`"<sup><a href=\"#,label\" name=\",|label|_anchor\"><font size=\"-1\">[訳註,|nlab|]</font></a></sup>"))))

(define (process input output)
  (process-block (lambda () (read-line input))
                 (lambda (line)
                   (display (process-line line) output)
                   (newline output))))

(define (main args)
  (let* ((output (current-output-port))
         (args   (parse-options (cdr args)
                   (("o=s" (file) (set! output file)))))
         (input  (if (null? args)
                     (current-input-port)
                     (car args))))
    (file-filter process :input input :output output))
  0)

;; Local variables:
;; mode: scheme
;; end:

Shiro (2005/02/05 03:44:44 PST): 似たようなものですが(というか上のスクリプトを コピペしてちょっといじっただけ)、こちらは『ハッカーと画家』 (Shiro:HackersAndPainters)の翻訳原稿の準備に使ったスクリプトです。

『ハッカーと画家』のケースでは、原書のLaTeXソースが入手できたので、 それに "@en" "@jp" の言語タグを入れてパラグラフ毎に翻訳していきました。 スクリプトを通すと英語部分がTeXのコメントになるようになっています。

また、書籍版には用語解説と索引があり、これを邦訳版では訳語の辞書順に 並べ替えなければなりません。どうせスクリプトを通すので、並べ替えも その時にやってしまうことにしました。具体的には、 "@beginsort" と "@endsort" に囲まれた部分について、"@key" で訳語の読みを指定し、それ以降の パラグラフ群 (次の "@key" または "@endsort" まで) をひとまとまりと して、"@key" で指定された読みの辞書順に並べ替えます。

例えば用語解説の最初の部分の原稿はこんな感じになってます。

@beginsort
@key ちゅうしょうてき
@en
\item[abstract]  Hiding details.  When a language is more
abstract, you can write programs using a smaller number
of (individually more powerful) operations.
@jp
\item[抽象的]   詳細を隠すこと。言語がより抽象的になると、
プログラムをより少ない操作で書くことができるようになる
(ひとつひとつの操作がより強力になる)。

@key ADA
@en
\item[Ada] An {\em object-oriented} language designed by a \ix{gada}
committee for the DoD in the late 1970s.  Turned out about
like you would expect.
@jp
\item[Ada] 1970年代後半に、国防省の主導で、委員会によって設計された
\term{オブジェクト指向}言語\ix{gada}。
色々な意味で予想通りのものとなった。

...

スクリプトはこんな感じ。

#!/usr/bin/gosh

;; Usage
;;
;;   transhp [-o output] input-file

(use srfi-13)
(use file.filter)
(use gauche.parseopt)

(define *jlabels* '())
(define *jnotes* '())

(define (process input output)
  (with-input-from-port input
    (lambda ()
      (with-output-to-port output
        in-normal))))

(define (checkdir line)
  (and (string-prefix? "@" line)
       (errorf "~a:~a:unknown directive: '~a'"
               (port-name (current-input-port))
               (- (port-current-line (current-input-port)) 1)
               line)))

(define (in-normal)
  (define (in-en line)
    (cond ((eof-object? line))
          ((string=? "@beginsort" line) (in-sort))
          ((string=? "@en" line) (in-en (read-line)))
          ((string=? "@jp" line) (in-jp (read-line)))
          ((checkdir line))
          (else (print "%-" line)
                (in-en (read-line)))))
  (define (in-jp line)
    (cond ((eof-object? line))
          ((string=? "@beginsort" line) (in-sort))
          ((string=? "@en" line) (in-en (read-line)))
          ((string=? "@jp" line) (in-jp (read-line)))
          ((checkdir line))
          (else (print line)     (in-jp (read-line)))))
  (in-jp (read-line)))

(define (in-sort)
  (define tab (make-hash-table 'string=?))
  (define (endsort)
    (dolist (key (sort (hash-table-keys tab) string<))
      (for-each print (ref tab key))))
  (define (flush rlines)
    (let1 z (reverse rlines)
      (hash-table-put! tab (car z) (cdr z))))
  (define (erreof)
    (error "EOF during @beginsort"))
  (define (newkey match acc loop)
    (unless (null? acc) (flush acc))
    (loop (read-line) (list (match 'after))))
  (define (in-en line acc)
    (cond ((eof-object? line) (erreof))
          ((string=? "@en" line) (in-en (read-line) acc))
          ((string=? "@jp" line) (in-jp (read-line) acc))
          ((#/^@key\s+/ line) => (cut newkey <> acc in-en))
          ((string=? "@endsort" line)
           (flush acc) (endsort) (in-normal))
          ((checkdir line))
          (else (in-en (read-line) (cons #`"%-,line" acc)))))
  (define (in-jp line acc)
    (cond ((eof-object? line) (erreof))
          ((string=? "@en" line) (in-en (read-line) acc))
          ((string=? "@jp" line) (in-jp (read-line) acc))
          ((#/^@key\s+/ line) => (cut newkey <> acc in-jp))
          ((string=? "@endsort" line)
           (flush acc) (endsort) (in-normal))
          ((checkdir line))
          (else (in-jp (read-line) (cons line acc)))))
  (in-jp (read-line) '()))

(define (main args)
  (let* ((output (current-output-port))
         (args   (parse-options (cdr args)
                   (("o=s" (file) (set! output file)))))
         (input  (if (null? args)
                     (current-input-port)
                     (car args))))
    (file-filter process :input input :output output))
  0)

;; Local variables:
;; mode: scheme
;; end:
More ...