Skip to Content

Bash脚本使用gettext实现l10n

labrador 的头像
注意,这篇文章中的内容有点过时,与中文化开发包中的实现方法不完全一样,这里仅供参考。

Bash也是可以支持gettext以实现l10n的。比如
LANG=zh_CN.utf8
TEXTDOMAIN="test"
TEXTDOMAINDIR="/usr/share/locale"
echo $"Add"
这里在显示Add的时候,会先去/usr/share/locale/zh_CN/~LC_MESSAGES/test.mo搜索,看看能否将Add翻译成汉语显示出来。根据这个脚本很容易可以生成相应的.po文件以供翻译者翻译,用法如下
bash --dump-op-strings <脚本名>
这很类似于python的pygettext。这里有个小问题,dump出来的.po文件中不包含codeset等信息,放在poedit里会出现乱码,所以还要自己加一个头,形如
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: zh_CN\n"
"POT-Creation-Date: 2008-05-22 00:15+EDT\n"
"PO-Revision-Date: 2008-05-22 18:41-0500\n"
"Last-Translator: laborer <laborer@126.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
另外有资料说xgettext也可以用于从bash脚本生成.po文件。

这里还有一个辅助翻译的emacs脚本,很简单的东西,就是选定字符串中要翻译的部分以后按F4,emacs会在选定的部分开头和结尾分别加上"$""",这样选定的这部分就从原来的字符串中独立出来,并且被标记为可翻译
(add-hook 'sh-mode-hook
	  (lambda () 
	    (local-set-key 
	     [f4]
	     (lambda () 
	       (interactive)
	       (insert-pair nil "\"$\"" "\"\"")))))

PuppyLinux 4.0自带的bash不支持自动翻译,需要用自己编译的。


很不幸,上述的方法已经deprecated了。另外更加不幸的是,我这一段已经写过一遍,结果没有保存,现在还要返工,郁闷。。。

这个方法被标记过时的原因是bash的$""语法有潜在的安全隐患,翻译者有可能自己加入代码到$""中间去执行。另外由于$""一直都是bash没有公开的特性,虽然可以这么用,但它从来没有出现在任何正式的文档中。所以出了这样的隐患,似乎大家都没有兴趣直接在bash里修理这个问题,而是用了eval_gettext来替代,方法如下:
LANG=zh_CN.utf8
TEXTDOMAIN="test"
TEXTDOMAINDIR="/usr/share/locale"
. /usr/bin/gettext.sh
echo "`eval_gettext "Add"`"
eval_gettext是在第四行的gettext.sh脚本中定义的,所以需要先载入这个脚本。后面eval_gettext部分看似也就比$""方法复杂了一点点,但在实际使用中要复杂了很多。因为字符串中的一些符号会在第一个双引号中被bash解析一遍,之后又被eval_gettext解释一遍,所以很多都需要加额外的escape character,比如,原来在字符串中使用的\"要写成\\\"$X要写成\\\$X。单引号似乎不需要做什么改变,至少我测试的结果是这样,虽然很多文档上说也要用\修饰。由于各个脚本解释器可能在解析字符串中的内容时有所差别。所以使用这个方法有可能降低程序的可移植性。

生成.po的方法也有所不同,因为bash不认得eval_gettext语法,--dump-po-strings也就无法获得想要的结果。这里就需要前面提到的xgettext,方法如下
xgettext -L Shell -o SCRIPT_NAME.po SCRIPT_NAME 
生成出的.po文件已经包含头了,不需要另外再加。不过还是需要自己填写一下头中的charset字段。用这个方法就不需要再另外编译bash了。

由于用eval_gettext方法,字符串里的某些字符需要额外的escape处理,这就使得标记需翻译段落的emacs脚本复杂了很多。用法还是和前面的一样。另外,标记过的字符串中会产生一大堆\\\,看上去可读性下降了一些。
(add-hook 'sh-mode-hook
	  (lambda () 
	    (local-set-key 
	     [f4]
	     (lambda (beg end) 
	       (interactive "r")
	       (when mark-active
		 (setq bound (max beg end))
		 (setq start (min beg end))
		 (goto-char start)
		 (while (search-forward "\\\"" bound t)
		   (setq pos (point))
		   (replace-match "\\\\\\\\\\\\\"")
		   (setq bound (+ bound (- (point) pos))))
		 (goto-char start)
		 (while (search-forward "$" bound t)
		   (setq pos (point))
		   (replace-match "\\\\\\\\\\\\$")
		   (setq bound (+ bound (- (point) pos))))
		 (goto-char start)
		 (push-mark start)
		 (goto-char bound)
		 (insert-pair nil "\"\"`eval_gettext \"" "\"`\"\"")
		 )))))
下面这个emacs脚本是搜寻程序中形如
MSG="`Xdialog ...`"
并替换为可以使用eval_gettext的形式
MSG="Xdialog ..."
MSG="`$MSG`"
(global-set-key 
 [f5]
 (lambda () 
   (interactive)
   (let ((var nil)
	 (start nil)
	 (bound nil)
	 (pos nil))
     (transient-mark-mode nil)
     (search-forward "=\"`Xdialog")
     (search-backward "=")
     (setq start (point))
     (beginning-of-line)
     (setq var (buffer-substring-no-properties (point) start))
     (push-mark)
     (search-forward "`\"\n")
     (transient-mark-mode t)
     
     (when (y-or-n-p "fix this? ")
       (setq bound (- (point) 1))
       (open-line 1)
       (insert var "=\"`$" (car (split-string var)) "`\"")
       (goto-char (- bound 2))
       (delete-char 1)
       (goto-char (+ start 2))
       (delete-char 1)
       (setq start (+ start 2))
       (setq bound (- bound 3))
       (goto-char start)
       (while (re-search-forward "[^\\]\"" bound t)
	 (backward-char 1)
	 (insert "\\")
	 (setq bound (+ bound 1))
	 )
       )
     )))
#16242
我没看懂额 怎么办

发表新评论

  • 你可以在文本中使用BBCode标记语言。 URL会自动被转为链接。

更多关於格式化选项的信息

CAPTCHA
请验证您是否是机器人。
Image CAPTCHA
Enter the characters shown in the image.