Emacs でテキスト翻訳をする

以前作った, エキサイト翻訳をするやつを更新した.
ほぼ全修正.
いやーいじったね.
エキサイトだけでなく, Google Translation も使えるようにした.
# ホントは, altavista, worldlingo も足したかったけど, 何度試しても上手くいかないからあきらめた.



まずは, Ruby スクリプト.
これを使って, 翻訳サイトへ繋いで, 翻訳をする.
以前作ったクラスが残ってないw

# 翻訳サイトを利用して, テキストを翻訳する Ruby プログラム.
# 文字コードに注意

# ============================================================
# コマンドラインからの使用法
# ============================================================
#
# このファイルの名前を translate.rb とし, ~/lib/ 以下に配置したとする.
#
# エキサイト翻訳 :
#  英語   → 日本語 excite_enja
#  日本語 →   英語 excite_jaen
# Google Translation :
#  英語   → 日本語 google_enja
#  日本語 →   英語 google_jaen
#
# ------------------------------------------------------------
# $ ruby ~/lib/translate.rb excite_enja "Japanese"
# ------------------------------------------------------------
#
# excite_enja のところを, 別のもの,
# google_enja 等に変更すれば, 他の翻訳サイトで翻訳をする.
# 翻訳した文字は, utf-8 で返戻される.
#
# ============================================================

require "net/http"
require "kconv"

class TranslateByWeb
  attr_reader :sites

  def initialize ()
    @sites = Hash.new
    # サイトの登録
    @sites["excite_enja"] = register_site( "www.excite.co.jp", "/world/english/", "wb_lp=ENJA&before=%s",
                                          "<input type=\"hidden\" name=\"after\" value=\"([^\"]*)", "sjis" )
    @sites["excite_jaen"] = register_site( "www.excite.co.jp", "/world/english/", "wb_lp=JAEN&before=%s",
                                          "<input type=\"hidden\" name=\"after\" value=\"([^\"]*)", "sjis" )
    @sites["google_enja"] = register_site( "translate.google.com", "/translate_t", "langpair=en|ja&ie=utf-8&oe=utf-8&text=%s",
                                           "<div id=result_box dir=ltr>([^<]*)", "utf-8" )
    @sites["google_jaen"] = register_site( "translate.google.com", "/translate_t", "langpair=ja|en&ie=utf-8&oe=utf-8&text=%s",
                                           "<div id=result_box dir=ltr>([^<]*)", "utf-8" )
  end

  def register_site( site, path, header, regex, code )
    name = SiteData.new
    name.site   = site
    name.path   = path
    name.header = header
    name.regex  = regex
    name.code   = code
    return name
  end

  def return_code ( code, word )
    case code
    when "sjis"
      return word.to_s.tosjis
    when "utf-8"
      return word.to_s.toutf8
    when "eucjp"
      return word.to_s.toeuc
    when "jis"
      return word.to_s.tojis
    else
      return word.to_s.toutf8
    end
  end

  def translate( site, word )
    site_data = @sites["excite_enja"]
    @sites.each do |key, val|
      if key == site
        site_data = val
      end
    end
    Net::HTTP.start( site_data.site, 80 ) do |http|
      response = http.post( site_data.path,
#                            site_data.header + return_code( site_data.code, word ) )
                            site_data.header.sub( /%s/, return_code( site_data.code, word ) ) )
      # html ソースがちゃんと取れているか確認用
#      puts response.body
      regex = Regexp.new( site_data.regex )
      if regex =~ response.body
#        puts $1.toutf8
        html_to_text( $1.toutf8 )
      end
    end
  end

  # 二重引用符や引用符, 不等号を示す特殊文字を変換する.
  def html_to_text ( string )
    # 引用符を変換
    string = string.gsub( "&#39;", "'" )
    # 二重引用符を変換
    string = string.gsub( "&quot;", "\"" )
    # アンドを変換
    string = string.gsub( "&amp;", "&" )
    # 不等号を変換
    string = string.gsub( "&lt;", "<")
    string = string.gsub( "&gt;", ">")
    puts string
  end

end

class SiteData
  attr_accessor :site, :path, :header, :regex, :code

  def initialize ()
    @site   = ""
    @path   = ""
    @header = ""
    @regex  = ""
    @code   = "utf-8"
  end
end

# テスト用
#TranslateByWeb.new.translate( "excite_jaen", "やっと, エキサイト翻訳を使って, 英語から日本語へ翻訳できた.
#やった.
#疲れたよ, 本当." )
#TranslateByWeb.new.translate( "google_enja", "Japanese " )

# コマンドラインから使用するサイトとテキストを取得して, 翻訳する.
tr = TranslateByWeb.new
tr.sites.keys.each do |site|
  if site == ARGV[0]
    tr.translate( site.to_s, ARGV[1].to_s )
  end
end

コマンドライン上から使用する場合は, 次のような感じで.

$ ruby ~/lib/translate.rb "excite_enja", "翻訳したい文字列"

変数 @sites に翻訳形式を増やしてあげれば, 日本語から英語とその逆だけじゃなくて,
フランス語に翻訳とかもできると思う. 試してないけど(文字コードで問題がでるかな?).
ちなみに, 翻訳したテキストは, utf-8 で返戻するようにしてる.


それで, こっちが elisp コード.
こっちも関数名からして, 全修した. 挙動も変わってる.
関数 text-translate が翻訳をする関数.
挙動はこんな感じ.

  • マークが active だった.
    • 前置引数(C-u)あり.
      選択した翻訳形式で, リージョンに入れた内容の翻訳を行う.
    • 前置引数(C-u)なし.
      リージョンに入れた内容を excite の英語→日本語翻訳で翻訳する.
  • マークが active でなかった.
    • 前置引数(C-u)あり.
      選択した翻訳形式で, ミニバッファより入力したテキストの翻訳を行う.
    • 前置引数(C-u)なし.
      ミニバッファより入力したテキストを excite の英語→日本語翻訳で翻訳する.
(defun text-translate (arg)
  "Excite 翻訳や Google Translation を使って, テキスト翻訳をする関数.
1. mark が active だった.
 - 前置引数が与えられた :
   1. 使用する翻訳サイトを選択する.
   2. 選択した形式で翻訳する.
 - 前置引数が与えられなかった :
   excite_enja でリージョンに入れた範囲を翻訳する.

2. mark が deactive だった.
 - 前置引数が与えられた.
   1. 使用する翻訳サイトを選択する.
   2. 選択した形式でミニバッファから入力した値を翻訳する.
 - 前置引数が与えられなかった.
   excite_enja でミニバッファから入力した値を翻訳する."
  (interactive "P")
  (let* ((sites '(("excite_enja") ("excite_jaen") ("google_enja") ("google_jaen")))
         (def   (caar sites)))
    ;; 前置引数があったならば, 使用する翻訳形式を選択したものに変更.
    (when arg
      (setq def (completing-read (format "select translation type [default:%s] : " def) sites nil t nil nil def)))
    (if mark-active
        (progn
          (translate-by-website def (buffer-substring-no-properties (region-beginning) (region-end)))
          (setq mark-active nil))
      (translate-by-website def (read-string (format "translate[%s] : " def))))))

(defun translate-by-website (type string)
  "Ruby スクリプト translate.rb を使用して, 翻訳をする関数."
  (let ((replace-list '(("`" "‘") ("\\$" "$") ("&" "&") (";" ";") ("%" "%"))))
    ;; 直接使用できない文字を全角に変換する.
    (mapc (lambda (x)
            (let ((num 0))
              (while (setq num (string-match (car x) string num))
                (setq string (replace-match (nth 1 x) nil nil string)))))
          replace-list)
    ;; 翻訳して, *translated* バッファへ表示.
    (display-buffer (get-buffer-create "*translated*"))
    (shell-command (concat "ruby ~/lib/translate.rb " type " " (prin1-to-string string)) "*translated*")))

まず, 当然だけど, 翻訳するためには, 先に記述した Ruby スクリプトが必要.
ここでは, ~/lib 以下に, translate.rb という名前で配置している.
これを変更したいならば, 上記コード, 最終行にある, 「~/lib/translate.rb」の部分を変更すれば大丈夫(なはず).
また, デフォの翻訳が excite_enja はイヤだって場合は, 以下の部分の順番を変更すれば変わる(と思う. なんせ試してないから(^^;))

  (let* ((sites '(("excite_enja") ("excite_jaen") ("google_enja") ("google_jaen")))

例えば, デフォルトを google_jaen にするならば, 以下のようにする.

  (let* ((sites '(("google_jaen") ("excite_enja") ("excite_jaen") ("google_enja")))

まあ, sites の第1引数がデフォになるってこと.
あ, あと, Ruby スクリプトに翻訳形式を足したならば, この sites 変数にも足した翻訳形式を足さないと駄目.



elisp の方で shell-command 使ってるから, ; とか & とかが翻訳したいテキストに含まれてたら, 上手くいかないという事態になってた.
バックスラッシュでクォートしようと頑張ってみたけど, 全然駄目だから,
強引に全角文字に変換することで対処した.
今じゃ, 意外と悪くないかもと思ったりしてる.

追記

plus さんからのメールで助かった.
html_to_text 関数がおかしいとの連絡.
おかしいな. 変更したはずないのに.
# 書いた文章は, 全部テキストファイルに保存してあるから, それ見たらちゃんとなっていた.
# 今日(2/20)は, 18時まで寝てたからその間に編集するわけないし・・・.
# って, 私以外の誰が編集するんだwww
とりあえず, ruby スクリプトの html_to_text 関数を正しいのに修正しておいた.