`

Java Web Application 另类的国际化方式gettext - commons for Java

 
阅读更多
本文是用在window系统下面使用linux的国际化方式,来实现项目的国际化。
因为项目原本没有实现国际化,都是中文的硬编码,到后期的时候,才决定做国际化。
不想去抽取所有硬编码写到properties文件,所以只得考虑用gettext-commons工具来实现。
使用到的工具:
gettext - commons : https://code.google.com/p/gettext-commons/
GetText for Windows:
资源:http://gnuwin32.sourceforge.net/packages/gettext.htm
安装:http://blog.longwin.com.tw/2010/08/windows-gettext-2010/

附件包含所用到的jar
扫描的时候,也可以扫描js等文件,但是要是以单引号标志的字符串,是得不到支持的。
js国际化可以考虑:
1.gettext-js https://code.google.com/p/gettext-js/wiki/MainDocumentation
2.Javascript Gettext http://jsgettext.berlios.de/


大致过程:
-------------------------------------------------------
GNU Gettext - commons 大致过程:
过程:
--->修改java的硬编码
--->msginit得到po文件
--->msgfmt命令得到class文件
--->反编译class
--->替换原有的java文件(所有多语言内容都会放在java的数组里面)
--->运行


gettext-commono整理

步骤:
1.配置gettext-0.14.4-bin,记得dll文件存放到bin下面去,两个dll文件(libexpat.dll,libiconv2.dll)。
————————————————————————————————————————————————————

2.配置gettext-commons
————————————————————————————————————————————————————
https://code.google.com/p/gettext-commons/downloads/list下载gettext-commons-0.9.6.jar,gettext-ant-tasks-0.9.7.jar
gettext-commons-0.9.6.jar:就是国际化的工具,这个放到项目的lib下面去。
gettext-ant-tasks-0.9.7.jar:提供给Ant的来运行msginit,msgfmt命令,这个随便放,但是build.xml的时候会制定它的路径。

src/i18n.properties
说明:在com.i18n包下面,存在Message_zh_CN.class的文件
basename=com.i18n.messages.Messages
#说明class文件要存放在com/i18n/messages下面

3.修改源码:
————————————————————————————————————————————————————
先创建一个实例:I18n i18n = I18nFactory.getI18n(getClass());
然后用它的tr(string),tr(string,object[])方法来实现国际化。
只注意这里两个方法,其他的类似C语言里面的东西,代码只是测试,不用理会。
第一个方法是没有带参数的国际化方法,第二个是带参数的国际化方法。
public void test(){
		
		I18n i18n = I18nFactory.getI18n(getClass());
		I18n i18n = I18nFactory.getI18n(getClass());
		System.out.println(i18n.tr("This is a test message1"));//返回翻译
		String[] array = new String[]{"Pandy","Brian"};
		System.out.println(i18n.tr("这句话是中文原文,第一个参数{0},第二个参数{1}",array));//返回翻译
		System.out.println(i18n.trn("This is a test message2","This is a test message2-2",0));
		System.out.println(i18n.trc("This is a test message3","this is a default message"));//返回默认
	}



4.在ant用gettext-extract命令生成po文件。build.xml文件
————————————————————————————————————————————————————
<?xml version="1.0"?>

<project name="ApplicationDemos" default="generate-default-bundle" basedir=".">
	<!-- properies -->
	<property name="src.otherLibs" value="otherLibs" />
	<property name="src.src" value="src" />
	<property name="src.bin" value="bin" />
	<property name="src.poFile" value="Message_zh_CN.po" />
	<property name="src.poDirectory" value="src/com/i18n/messages" />
	<property name="src.targetBundle" value="com.i18n.messages.Messages_zh_CN" />
	<property name="src.poTmpDir" value="po" />


	<!-- 定义classpath -->
	<path id="classpathGettext">
		<fileset file="${src.otherLibs}/gettext-ant-tasks-0.9.7.jar" />
	</path>
	<!-- <property name="gettexttasks.jar" value="${src.otherLibs}/gettext-ant-tasks-0.9.3.jar" /> -->
	<!-- 国际化抽取和实现的任务 -->
	<!-- 生成Po文件 -->
	<taskdef name="gettext-extract" classname="org.xnap.commons.ant.gettext.GettextExtractKeysTask" classpathref="classpathGettext" />
	<!-- 合并Po文件 -->
	<taskdef name="gettext-merge" classname="org.xnap.commons.ant.gettext.GettextMergeKeysTask"  classpathref="classpathGettext" />
	<!-- 生成默认的class文件 -->
	<taskdef name="gettext-generate-default" classname="org.xnap.commons.ant.gettext.GenerateDefaultBundleTask"  classpathref="classpathGettext"/>
	<!-- 为生成jar方式的国际化文件 -->
	<taskdef name="gettext-dist" classname="org.xnap.commons.ant.gettext.GettextDistTask"  classpathref="classpathGettext"/>

	<!-- 初始化任务-->
	<target name="initI18N">
	</target>

	<target name="init.gettext" description="Loads the Ant gettext tasks">
	</target>
	<!-- 生成po文件 -->
	<target name="messages-extract" description="Extracts message keys from the source code" depends="init.gettext">
		<gettext-extract keysFile="${src.poFile}" poDirectory="${src.poDirectory}" encoding="UTF-8">
			<fileset dir="src" includes="**/*.java"/>
		</gettext-extract>
	</target>
	<!-- 合并po文件 -->
	<target name="messages-merge" description="Merges newly extracted messages into existing po files" depends="init.gettext">
	  <gettext-merge keysFile="${src.poFile}" poDirectory="${src.poDirectory}"/>
	</target>
	<!-- 生成默认的class文件 -->
	<target name="generate-default-bundle" description="Generates a default bundle" depends="init.gettext">
		<gettext-generate-default targetBundle="${src.targetBundle}" outputDirectory="${src.poTmpDir}" potfile="${src.poDirectory}/${src.poFile}"/>
	</target>
	<!-- 生成默认的jar文件 -->
	<target name="generate-bundles-jar" description="Generates Java ResourceBundles and jars them up" depends="init.gettext">
	  <gettext-dist targetBundle="${src.targetBundle}" poDirectory="${src.poTmpDir}" outputDirectory="${src.poTmpDir}" percentage="65"/>
	  <jar destfile="lib/messages.jar" basedir="${src.poTmpDir}" includes="org/**"/>
	</target>

	<!-- 编译-->
	<target name="compile" depends="initI18N" description="compile the source files">
		<mkdir dir="${src.bin}" />
		<javac srcdir="${src.src}" destdir="${src.bin}" target="6.0" includeantruntime="true">
			<classpath refid="classpath" />
		</javac>
	</target>

</project>


上面是编译命令,使用messages-extract命令,得到下面的po文件,存放在src/com/i18n/messages/Message_zh_CN.po,   这个路径会在生成class文件的时候用到,
记得修改版本号,不修改也不影响?没试过。

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-08-19 20:00+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: src\com\i18n\I18nTest.java:16
msgid "This is a test message1"
msgstr ""

#: src\com\i18n\I18nTest.java:18
#, java-format
msgid "这句话是中文原文,第一个参数{0},第二个参数{1}"
msgstr ""

#. 返回翻译
#: src\com\i18n\I18nTest.java:19
msgid "This is a test message2"
msgid_plural "This is a test message2-2"
msgstr[0] ""
msgstr[1] ""



5.对po文件人工翻译。其实,因为源码本来都是硬编码,所以通常只需要直接把每一个msgid的东西,复制给msgstr。就可以了。
————————————————————————————————————————————————————
"Project-Id-Version: 0.1\n"

#: src\com\i18n\I18nTest.java:16
msgid "This is a test message1"
msgstr "翻译:第一句翻译"

#: src\com\i18n\I18nTest.java:18
#, java-format
msgid "这句话是中文原文,第一个参数{0},第二个参数{1}"
msgstr "翻译:这句话是中文原文,第一个参数{0},第二个参数{1}"

#. 返回翻译
#: src\com\i18n\I18nTest.java:19
msgid "This is a test message2"
msgid_plural "This is a test message2-2"
msgstr[0] "翻译:参数第一句翻译"
msgstr[1] "翻译:参数第二句翻译"



6.生成class文件:
————————————————————————————————————————————————————
生成class的第一种方式:
msgfmt --java2 -d D:\dev-workspace\workspace\ApplicationDemos\po -r com.i18n.Messages -l zh_CN D:\dev-workspace\workspace\ApplicationDemos\src\com\messages\messages.po
格式化po文件:格式化得到的class文件存放到po文件夹下面,按照com.i18n路径存放为Messages+zh_CN.class,po的存放位置:D:\dev-workspace\workspace\ApplicationDemos\src\com\messages\messages.po
po的路径在上面已经指定了。

生成class的第二种方式(推荐):
=====>可以在Ant下面用命令
<target name="generate-default-bundle" description="Generates a default bundle" depends="init.gettext">
		<gettext-generate-default targetBundle="${src.targetBundle}" outputDirectory="${src.poTmpDir}" potfile="${src.poDirectory}/${src.poFile}"/>
</target>


7.原来class的java文件内容,要手动建立这个类,然后等待下面的步骤替换:这个在cygwin里面编译看见的java. 应该是一个标准的class模板。当我们用GetText for Windows,然后用ant命令得到class文件的时候,默认会删除java文件,直接给出class文件。要是没有发现cygwin里面的java文件,那么可能要傻傻的去做反编译。它得到两个class文件,其中一个class是另一个class的匿名类。

————————————————————————————————————————————————————
src/com/i18n/Messages_zh_CN.java
package com.i18n.messages;

import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

public class Messages_zh_CN extends ResourceBundle {
	private static final String[] table;//要注意的地方:当没有参数的国际化的时候,这里是String[]类型,否则是Object[]类型。

	public Object handleGetObject(String paramString) throws MissingResourceException {
		int i = paramString.hashCode() & 0x7FFFFFFF;
		int j = i % 1 << 1;//要注意的地方:中间的那个数字,要被替换的,因为它跟国际化信息的记录数有关
		String str = table[j];
		if ((str != null) && (paramString.equals(str)))
			return table[(j + 1)];
		return null;
	}

	public Enumeration getKeys() {
		return new java.util.Enumeration() {//要注意的地方:这里就是生成的一个class匿名类的代码,只要注意,但不被替换。
			private int idx = 0;
			{
				while (idx < 2 && table[idx] == null)
					idx += 2;
			}

			public boolean hasMoreElements() {
				return (idx < 2);
			}

			public java.lang.Object nextElement() {
				java.lang.Object key = table[idx];
				do
					idx += 2;
				while (idx < 2 && table[idx] == null);
				return key;
			}
		};
	}

	public ResourceBundle getParent() {
		return this.parent;
	}
      //要注意的地方:这里就是国际化的信息,会保存到一个数组,而不是properties,这里就是特别的地方
	static {
		String[] arrayOfString = new String[2];
		arrayOfString[0] = "";
		arrayOfString[1] = "Project-Id-Version: 0.01\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-08-17 14:38+0800\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\nLast-Translator: FULL NAME <EMAIL@ADDRESS>\nLanguage-Team: LANGUAGE <LL@li.org>\nLanguage: \nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n";
		table = arrayOfString;
	}
}



8.反编译得到的java去替换上面的java模板。
到ApplicationDemos\po\com\i18n去看到Messages_zh_CN.class,Messages_zh_CN$1.class两个class文件,后面的那个class其实是前面class的一个内部类。
————————————————————————————————————————————————————
反编译:Messages_zh_CN.class:
package com.i18n.messages;

import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

public class Messages_zh_CN extends ResourceBundle
{
  private static final Object[] table; //拿这里去java模板替换

  public static final String[] get_msgid_plural_table()
  {
    return new String[] { "This is a test message2-2" };
  }
  public Object lookup(String paramString) {
    int i = paramString.hashCode() & 0x7FFFFFFF;
    int j = i % 4 << 1; //拿这里去java模板替换
    Object localObject = table[j];
    if ((localObject != null) && (paramString.equals(localObject)))
      return table[(j + 1)];
    return null;
  }
  public Object handleGetObject(String paramString) throws MissingResourceException {
    Object localObject = lookup(paramString);
    return (localObject instanceof String[]) ? ((String[])(String[])localObject)[0] : localObject;
  }
  public Enumeration getKeys() {
    return new Object() {
      private int idx;

      public boolean hasMoreElements() {
        return this.idx < 22;
      }
      public Object nextElement() {
        Object localObject = Messages_zh_CN.table[this.idx];
        do this.idx += 2; while ((this.idx < 22) && (Messages_zh_CN.table[this.idx] == null));
        return localObject;
      } } ;
  }

  public static long pluralEval(long paramLong) {
    return paramLong != 1L ? 1 : 0;
  }
  public ResourceBundle getParent() {
    return this.parent;
  }

  //拿这里去java模板替换
    static
  {
    Object[] arrayOfObject = new Object[8];
    arrayOfObject[0] = "";
    arrayOfObject[1] = "Project-Id-Version: PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-08-19 20:00+0800\nPO-Revision-Date: 2012-08-19 20:00+0800\nLast-Translator: Automatically generated\nLanguage-Team: none\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n";
    arrayOfObject[2] = "This is a test message1";
    arrayOfObject[3] = "翻译:第一句翻译";
    arrayOfObject[4] = "This is a test message2";
    arrayOfObject[5] = { "翻译:参数第一句翻译", "翻译:参数第二句翻译" };//这里是反编译得到的,要进行修改,否则出错。
    arrayOfObject[6] = "这句话是中文原文,第一个参数{0},第二个参数{1}";
    arrayOfObject[7] = "翻译:这句话是中文原文,第一个参数{0},第二个参数{1}";
    table = arrayOfObject;
  }
}


反编译:Messages_zh_CN$1.class:这个不需要什么特别注意的地方
package com.i18n.messages;

import java.util.Enumeration;

class Messages_zh_CN$1
  implements Enumeration
{
  private int idx;

  public boolean hasMoreElements()
  {
    return this.idx < 22;
  }
  public Object nextElement() {
    Object localObject = Messages_zh_CN.access$000()[this.idx];
    do this.idx += 2; while ((this.idx < 22) && (Messages_zh_CN.access$000()[this.idx] == null));
    return localObject;
  }
}


可以看见Messages_zh_CN$1.class其实就是Messages_zh_CN.java的一部分。


9.替换java内容,Messages_zh_CN.class反编译得到的,去替换原来java的内容:
————————————————————————————————————————————————————
package com.i18n.messages;

import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

public class Messages_zh_CN extends ResourceBundle {
	//private static final String[] table;
	private static final Object[] table;//这里原来是String[],但是看Po文件,因为数组里面的元素,有存在一个数组的元素,所以这里变成Object[]

	public Object handleGetObject(String paramString) throws MissingResourceException {
		System.out.println("Messages_zh_CN_______________________1");
		int i = paramString.hashCode() & 0x7FFFFFFF;
	    int j = i % 11 << 1;//这里要注意:当翻译的数据变化的时候,i % 11 << 1 中间的11这个数组也会跟着变化,才能在数组里面找到真正的信息
	    Object localObject = table[j];
	    if ((localObject != null) && (paramString.equals(localObject)))
	      return table[(j + 1)];
	    return null;
	}

	public Enumeration getKeys() {
		System.out.println("Messages_zh_CN_______________________2");
		return new java.util.Enumeration() {
			private int idx = 0;
			{
				while (idx < 2 && table[idx] == null)
					idx += 2;
			}

			public boolean hasMoreElements() {
				return (idx < 2);
			}

			public java.lang.Object nextElement() {
				java.lang.Object key = table[idx];
				do
					idx += 2;
				while (idx < 2 && table[idx] == null);
				return key;
			}
		};
	}

	public ResourceBundle getParent() {
		System.out.println("Messages_zh_CN_______________________3");
		return this.parent;
	}

	static {
		System.out.println("Messages_zh_CN_______________________0");
		//下面其实就是po做msgfmt得到的信息,存放在一个数组里面,注意:第二个元素没有修改版本号,也没有设定编码类型,这个可能引起其他问题么?待解.....
		Object[] arrayOfObject = new Object[8];
    arrayOfObject[0] = "";
    arrayOfObject[1] = "Project-Id-Version: PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-08-19 20:00+0800\nPO-Revision-Date: 2012-08-19 20:00+0800\nLast-Translator: Automatically generated\nLanguage-Team: none\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n";
    arrayOfObject[2] = "This is a test message1";
    arrayOfObject[3] = "翻译:第一句翻译";
    arrayOfObject[4] = "This is a test message2";
    //arrayOfObject[5] = { "翻译:参数第一句翻译", "翻译:参数第二句翻译" };//这里是反编译得到的,要进行修改,否则出错。
    arrayOfObject[5] = new String[]{ "翻译:参数第一句翻译", "翻译:参数第二句翻译" };//修改,反编译的错误
    arrayOfObject[6] = "这句话是中文原文,第一个参数{0},第二个参数{1}";
    arrayOfObject[7] = "翻译:这句话是中文原文,第一个参数{0},第二个参数{1}";
    table = arrayOfObject;
	}
}



10.运行src/com/i18n/I18nTest.java类  查看国际化信息
————————————————————————————————————————————————————
I18n i18n = I18nFactory.getI18n(getClass());
		System.out.println(i18n.tr("This is a test message1"));//返回翻译
		String[] array = new String[]{"Pandy","Brian"};
		System.out.println(i18n.tr("这句话是中文原文,第一个参数{0},第二个参数{1}",array));//返回翻译
		System.out.println(i18n.trn("This is a test message2","This is a test message2-2",0));
		System.out.println(i18n.trc("This is a test message3","this is a default message"));//返回默认




最后输出:
=====================================>
翻译:第一句翻译
翻译:这句话是中文原文,第一个参数Pandy,第二个参数Brian
This is a test message2-2
this is a default message


这只是实现过程的记录,还没有完整整理,特别注意的地方是反编译后替换,这里最好写程序来实现编译后替换,就安全一点,不容易错误。
分享到:
评论

相关推荐

    gettext-commons:通过GNU gettext和Java ResourceBundles进行的国际化(i18n)

    《通过GNU gettext和Java ResourceBundles进行的国际化(i18n):gettext-commons深度解析》 在软件开发中,为了满足全球用户的需求,国际化(i18n)和本地化(l10n)成为了必不可少的步骤。其中,gettext-commons是Java...

    gettext-runtime-0.13.1

    1. **bin**:这个目录通常包含可执行文件或脚本,这些文件可以在命令行环境中直接运行,用于执行与gettext-runtime相关的功能,如国际化工具或者帮助开发者进行翻译工作的实用程序。 2. **README.gettext-runtime**...

    gettext-0.21.tar.gz

    gettext 是 GNU Translation Project上的重要一步,从它基础上可以构建其他步骤。 gettext提供了一个帮助产生多语言 message的框架:包括一组关于程序改如何编写以支持 message种类的约定, message种类相关的一个...

    gettext-0.14.4-二进制版本

    `gettext`是一个广泛使用的工具集合,用于处理软件的国际化(i18n)和本地化(l10n)问题。这个"gettext-0.14.4-二进制版本"是`gettext`工具的一个特定发行版,适用于处理和管理多语言环境中的文本资源。在IT领域,...

    gettext-static-0.19.8.1

    总之,`gettext-static-0.19.8.1`为Java开发者提供了一种高效、灵活的方式来实现项目的国际化,使得软件能够更好地适应全球用户的需求。了解和掌握`gettext`工具的使用,对于提升软件质量、增强用户体验具有重要意义...

    hugin编译文件gettext-0.13.1.tar.gz

    2. **gettext-runtime**:这是运行时库,包含了实现国际化和本地化(i18n和l10n)所需的基本函数和数据结构。这些函数允许软件开发者轻松地将可翻译的字符串从源代码中提取出来,然后在目标语言环境中使用。`gettext...

    gettext-msvc-master.zip

    `gettext-msvc-master.zip` 是一个专门为Windows操作系统编译的`gettext`开发库。`gettext`是一个广泛使用的软件本地化工具,它允许开发者轻松地将应用程序的文本字符串翻译成多种语言,以支持国际化(i18n)和本地...

    gettext-0.16.1.tar.gz

    `gettext-0.16.1.tar.gz` 是一个开源软件包,主要用于处理软件本地化(internationalization)和翻译(localization),它在IT行业中扮演着重要的角色,尤其是在多语言支持方面。`gettext` 工具集是GNU项目的一部分...

    gettext-0.19.8.1.tar.gz

    `gettext-0.19.8.1.tar.gz` 是一个在Linux环境下广泛使用的开源工具包,主要用于软件的国际化和本地化(I18N和L10N)。这个压缩包包含了`gettext`库和相关的工具,使得开发者能够轻松地为他们的应用程序添加多语言...

    gettext-0.18.1.1.tar.gz

    《gettext-0.18.1.1:Linux系统中的多语言支持库》 在Linux操作系统的世界里,软件本地化(Localization,简称L10n)是一个至关重要的议题,它使得全球用户能够根据自己的语言环境使用软件。"gettext"项目正是为了...

    gettext-0.19.4.tar.xz

    vlc 编译使用 gettext-0.19.4.tar.xz ,下载后先解压,再安装

    源码gettext-0.19.3.tar

    `gettext-0.19.3.tar` 是一个包含了 `gettext` 工具集源代码的压缩文件。`gettext` 是一个广泛使用的开源工具,主要用于软件的国际化(i18n,Internationalization)和本地化(l10n,Localization)。这个版本号表示...

    前端开源库-angular-gettext-tools

    Angular GetText工具,简称`angular-gettext-tools`,是前端开发中一个非常重要的开源库,专门针对Angular框架设计,旨在帮助开发者方便地提取、管理和编译应用中的国际化(i18n)文本。在多语言环境中,提供良好的...

    GLib (Run-time) ,gettext-runtime (Run-time) 、 pkg-config (tool),

    总之,GLib、gettext-runtime和pkg-config在Windows MingW环境下是进行C/C++开发的重要组成部分,它们提供了基础的数据结构、国际化支持以及管理库依赖的工具,使得跨平台开发变得更加便捷。在进行编译配置时,确保...

    gettext-tools-0.13.1

    《Python中的gettext-tools-0.13.1:本地化与国际化利器》 在软件开发过程中,为了满足全球用户的需求,本地化(Localization)和国际化(Internationalization)变得至关重要。`gettext-tools-0.13.1`是Python中一...

    smarty国际化 附smarty-gettext-0.9.1下载

    ### Smarty与Gettext实现国际化详解 在Web开发中,国际化(Internationalization)是构建多语言网站和应用程序的关键步骤。PHP的Smarty模板引擎结合Gettext工具,可以高效地实现这一目标。本文将深入解析如何利用...

    gettext-0.19.3.tar.xz

    2015/11/4 9:57 ubuntu-12.04-desktop-amd64.iso中源码编译VCL的时候,需要gettext-0.19.3或者更高版本,给有需要的同学!

    Gettext-0.13.1-win7-x64-vs2013

    Gettext-0.13.1-win7-x64-vs2013 是一个专为Windows 7 64位操作系统和Visual Studio 2013编译环境设计的Gettext工具包版本。Gettext是一个广泛使用的开源工具集合,主要用于处理软件的国际化(i18n)和本地化(l10n...

    gettext-0.17.tar

    `gettext-0.17.tar` 是一个包含 `gettext` 工具的源代码压缩包,版本号为0.17。`gettext` 是一个广泛使用的软件开发工具,主要用于国际化(i18n)和本地化(l10n)过程,使软件能够支持多种语言。以下是关于 `...

Global site tag (gtag.js) - Google Analytics