89、国际化与本地化开发指南

国际化与本地化开发指南

1. 国际化与本地化概述

在软件开发中,“一次编写,到处运行”的理念意味着代码要在不同语言和文化环境中正常工作。为了让程序能适应这些差异,需要进行国际化和本地化处理。

国际化(Internationalization)是使程序具有灵活性,能适应不同地区变化的过程。而本地化(Localization)则是使用国际化工具,将程序适配到特定地区,比如将消息翻译成当地语言。

国际化有一些工具可用。首先,语言本身就具备支持,字符串采用 Unicode 编码,几乎能表达地球上任何书面语言。不过,字符串仍需人工翻译,并且向用户显示翻译后的文本需要相应字符的字体。Unicode 对代码本地化有很大帮助。

国际化和本地化的核心概念是“区域设置”(Locale),它定义了一个“地方”,可以是语言、文化或国家等,只要有一套相关的习俗需要程序行为做出改变。每个运行的程序都有一个默认的区域设置,反映用户的偏好。程序应尽可能适应区域设置的习俗。区域设置由 java.util 包中的 Locale 类表示。

给定一个区域设置,有几个工具可帮助程序以当地可理解的方式运行。常见的模式是类定义执行区域敏感操作的方法。该类的通用“get instance”静态方法返回适合默认区域设置的对象(可能是子类的对象)。该类还会提供每个“get instance”方法的重载版本,接受一个区域设置参数并返回适合特定区域设置的对象。例如,通过调用类的 getInstance 方法,可以获得一个合适的 java.util.Calendar 对象,该对象能处理用户偏好的日期和时间。返回的 Calendar 对象会根据默认区域设置的习俗将系统时间转换为日期。如果用户是墨西哥人,可能会返回一个适应墨西哥习俗的 Calendar 对象;中国用户可能会得到一个按照中国历法习俗工作的不同子类的对象。

如果程序向用户显示信息,通常需要对输出进行本地化。对不懂英语的人说 “That’s not right” 可能毫无意义,因此需要将消息翻译成其他地区用户能理解的语言。资源束(Resource Bundle)机制通过将字符串键映射到任意资源,帮助实现这一点。使用资源束返回的值,程序就能用其他语言表达,而不是在代码中直接编写文字消息字符串,而是通过字符串键从资源束中查找字符串。当程序迁移到另一个区域设置时,有人可以翻译资源束中的消息,程序无需修改一行代码就能在新的区域设置下工作。

2. 区域设置(Locale)

java.util.Locale 对象描述了一个特定的地方,包括文化、政治或地理方面。使用区域设置,对象可以根据用户期望进行本地化行为。能做到这一点的对象称为区域敏感对象。例如,使用区域敏感的 DateFormat 类可以对日期格式进行本地化。在英国写成 26/11/72 的日期,在冰岛会写成 26.11.72,在美国是 11/26/72,在拉脱维亚则是 72.26.11。

一个单一的区域设置代表了语言、国家和其他传统方面的问题。对于美国英语、英国英语、澳大利亚英语、巴基斯坦英语等,可以有不同的区域设置。虽然这些区域设置的语言可能相同,但日期、货币和数字表示的习俗各不相同。

代码很少直接获取或创建 Locale 对象,而是使用反映用户偏好的默认区域设置。通常通过获取资源或资源束隐式地使用这个区域设置,就像其他区域敏感类那样。例如,获取默认日历对象的代码如下:

Calendar now = Calendar.getInstance();

Calendar 类的 getInstance 方法会查找默认区域设置来配置返回的日历对象。当编写自己的区域敏感类时,可以从 Locale 类的静态 getDefault 方法获取默认区域设置。

如果编写的代码允许用户选择区域设置,可能需要创建 Locale 对象。 Locale 类有三个构造函数:
- public Locale(String language, String country, String variant) :创建一个表示给定语言和国家的 Locale 对象。其中, language 是语言的双字母 ISO 639 代码(如 “et” 表示爱沙尼亚语), country 是国家的双字母 ISO 3166 代码(如 “KY” 表示开曼群岛)。变体可以指定任何内容,如操作环境(如 “POSIX” 或 “MAC”)、公司或时代。如果指定多个变体,用下划线分隔。若要不指定区域设置的某个部分,使用空字符串 "" ,而不是 null
- public Locale(String language, String country) :相当于 Locale(language, country, "")
- public Locale(String language) :相当于 Locale(language, "", "")

语言和国家代码可以是任意大小写,但为了符合标准,语言代码会转换为小写,国家代码会转换为大写,变体转换为大写。

Locale 类为几个知名的区域设置定义了静态 Locale 对象,如 CANADA_FRENCH KOREA 用于表示国家, KOREAN TRADITIONAL_CHINESE 用于表示语言。这些对象只是为了方便,与自己创建的任何 Locale 对象相比没有特殊权限。

静态方法 setDefault 可以更改默认区域设置。默认区域设置是共享状态,应始终反映用户的偏好。如果有代码必须在不同的区域设置下运行,可以在获取资源或进行特定操作时将该区域设置作为参数指定给区域敏感类。通常很少需要更改默认区域设置。

Locale 类提供了获取区域设置描述各部分的方法。 getCountry getLanguage getVariant 方法返回构造时定义的值,这些是大多数用户不熟悉的简洁代码。这些方法有 “display” 变体,如 getDisplayCountry getDisplayLanguage getDisplayVariant ,返回值的人类可读版本。 getDisplayName 方法返回整个区域设置描述的人类可读摘要, toString 方法返回简洁的等效表示,用下划线分隔各部分。这些 “display” 方法返回的值会根据默认区域设置进行本地化。

可以选择为任何 “display” 方法提供一个 Locale 参数,以获取给定区域设置在提供的区域设置下的描述。例如:

System.out.println(Locale.ITALY.getDisplayCountry(Locale.FRANCE));

输出结果为:

Italie

即意大利在法语中的名称。

getISO3Country getISO3Language 方法分别返回区域设置的国家和语言的三字符 ISO 代码。

3. 资源束(Resource Bundles)

在代码国际化过程中,通常有一些意义单元,如文本或声音,需要针对每个区域设置进行翻译或调整。如果直接将英文文本放入程序,对代码进行本地化会很困难,需要在程序中找到所有字符串,识别哪些是显示给用户的,然后在代码中进行翻译,从而为例如斯瓦希里语用户创建程序的第二个版本。当为大量区域设置重复这个过程时,任务会变得非常繁琐。

java.util 中的资源束类以更简洁、更灵活的方式帮助解决这个问题。抽象类 ResourceBundle 定义了通过字符串键在束中查找资源的方法,并提供了一个父束,如果束中没有某个键,会搜索父束。这种继承特性允许一个束与另一个束类似,只是修改或添加了一些资源值。例如,美国英语束可能使用英国英语束作为父束,为拼写不同的资源提供替换。 ResourceBundle 提供了以下公共方法:
| 方法 | 描述 |
| ---- | ---- |
| public final String getString(String key) throws MissingResourceException | 返回束中给定键对应的字符串。 |
| public final String[] getStringArray(String key) throws MissingResourceException | 返回束中给定键对应的字符串数组。 |
| public final Object getObject(String key) throws MissingResourceException | 返回束中给定键对应的对象。 |
| public abstract Enumeration getKeys() | 返回该束理解的键的枚举,包括父束的所有键。 |

每个资源束定义了一组字符串键,这些键映射到区域敏感资源。这些字符串可以是任意的,但最好具有记忆性。需要使用资源时,通过名称查找。如果找不到资源,会抛出 MissingResourceException 异常。资源本身可以是任何类型,但通常是字符串,因此提供了 getString 方法以方便使用。

下面是一个国际化的 “Hello, world” 示例:

import java.util.*;
public class GlobalHello {
    public static void main(String[] args) {
        ResourceBundle res = ResourceBundle.getBundle("GlobalRes");
        String msg;
        if (args.length > 0)
            msg = res.getString(GlobalRes.GOODBYE);
        else
            msg = res.getString(GlobalRes.HELLO);
        System.out.println(msg);
    }
}

程序首先获取其资源束,然后检查命令行是否提供了参数。如果有参数,程序说再见;否则说你好。程序逻辑决定显示哪个消息,但实际要打印的字符串是通过键( GlobalRes.HELLO GlobalRes.GOODBYE )查找的。

每个资源束是一组相关的类和属性文件。在这个示例中, GlobalRes 是一个继承自 ResourceBundle 的类的名称,它实现了将消息键映射到该消息的本地化翻译所需的方法。为想要本地化消息的各个区域设置定义类,类名反映区域设置。例如,管理 Lingala 语言的 GlobalRes 消息的束类将是 GlobalRes_ln ,因为 “ln” 是 Lingala 语的双字母代码。法语将映射到 GlobalRes_fr ,加拿大法语将是 GlobalRes_fr_CA ,它可能以 GlobalRes_fr 作为父束。

GlobalRes 类中,将键字符串定义为常量。使用常量可以防止拼写错误。如果将像 “hello” 这样的文字字符串传递给 getString 方法,拼写错误只有在执行错误的 getString 时才会显示出来,而且在测试期间可能不会出现这种情况。如果使用常量,拼写错误会被编译器捕获(除非不小心拼写成了另一个常量的名称)。

通过调用 ResourceBundle 中的两个静态 getBundle 方法之一来查找资源:一个是上述示例中使用的方法,它在当前区域设置中搜索你指定名称的束的最佳可用版本;另一个方法允许你同时指定束名称和所需的区域设置。完全限定的束名称形式为 package.Bundle_la_CO_va ,其中 package.Bundle 是束类的通用完全限定名称(如 GlobalRes ), la 是双字母语言代码(小写), CO 是双字母国家代码(大写), va 是用下划线分隔的变体列表。如果找不到具有完整名称的束,则去掉最后一个组件,用较短的名称再次搜索。这个过程会重复进行,直到只剩下最后一个区域设置修饰符。如果即使这样的搜索也失败了,并且你在调用 getBundle 时指定了区域设置,则使用默认区域设置的束的完整名称重新开始搜索。如果第二次搜索仍未找到束,或者你是在默认区域设置中搜索, getBundle 会仅使用束名称进行检查。如果连这个束也不存在, getBundle 会抛出 MissingBundleException 异常。

例如,假设你请求 GlobalRes 束,指定一个居住在基里巴斯的左撇子世界语使用者的区域设置,而用户的默认区域设置是在不丹为 Acme 公司工作的尼泊尔语使用者。最长可能的搜索顺序如下:
1. GlobalRes_eo_KI_left
2. GlobalRes_eo_KI
3. GlobalRes_eo
4. GlobalRes_ne_BT_Acme
5. GlobalRes_ne_BT
6. GlobalRes_ne
7. GlobalRes

找到的第一个资源束将结束搜索,被认为是最佳匹配。

前面的示例使用资源束来获取字符串,但要记住可以使用 getObject 方法获取任何类型的对象。束用于存储图像、URL、音频源、图形组件以及任何其他可以用对象表示的区域敏感资源。

将字符串键映射到本地化资源对象通常很直接,只需使用 ResourceBundle 提供的子类之一来实现查找功能: ListResourceBundle PropertyResourceBundle

4. 资源束子类
4.1 ListResourceBundle

ListResourceBundle 将简单的键列表映射到它们的本地化对象。它是 ResourceBundle 的抽象子类,需要提供一个 getContents 方法,该方法返回一个键/资源对数组,作为 Object 类型的二维数组。键必须是字符串,但资源可以是任何类型的对象。 ListResourceBundle 会使用这个数组为各种 “get” 方法构建映射。

以下是使用 ListResourceBundle GlobalRes 定义几个区域设置的类:

public class GlobalRes extends ListResourceBundle {
    public static final String HELLO = "hello";
    public static final String GOODBYE = "goodbye";
    public Object[][] getContents() {
        return contents;
    }
    private static final Object[][] contents = {
        { GlobalRes.HELLO,      "Ciao" },
        { GlobalRes.GOODBYE,    "Ciao" },
    };
}

这是顶级束,当找不到其他束时将使用它。这里选择意大利语作为默认语言。在执行任何 “get” 方法之前,会调用 GlobalRes.getContents 方法, contents 数组的值将为 “get” 方法使用的数据结构提供初始值。 ListResourceBundle 使用内部查找表进行高效访问,不会遍历键数组。 GlobalRes 类还定义了束中已知资源的常量名称。

以下是一个针对更具体区域设置的束:

public class GlobalRes_en extends ListResourceBundle {
    public Object[][] getContents() {
        return contents;
    }
    private static final Object[][] contents = {
        { GlobalRes.HELLO,      "Hello" },
        { GlobalRes.GOODBYE,    "Goodbye" },
    };
}

这个束覆盖了英语区域设置 “en”,为每个可本地化的字符串提供了特定的值。

下一个束使用了继承特性:

public class GlobalRes_en_AU extends ListResourceBundle {
    // mostly like basic English  - our parent bundle
    public Object[][] getContents() { return contents; }
    private static final Object[][] contents = {
        { GlobalRes.HELLO,      "G'day" },
    };
}

这个束针对澳大利亚(,“AU”)的英语使用者。它为 “HELLO” 字符串提供了更口语化的版本,并从通用英语区域设置 GlobalRes_en 继承所有其他字符串。每当实例化一个资源束时,会建立其父链。这通过从基本束名称中依次去掉变体、国家和语言组件,并实例化这些束(如果它们存在)来实现。如果它们存在,则在前面的束上调用 setParent 方法,将新束作为父束传递。所以在这个示例中,当创建 GlobalRes_en_AU 时,系统将创建 GlobalRes_en 并将其设置为 GlobalRes_en_AU 的父束。反过来, GlobalRes_en 的父束将是基本束 GlobalRes

给定这些类,具有英语区域设置(“en”)的人将获得 GlobalRes_en 返回的值,除非区域设置还指定了国家为澳大利亚(“AU”),在这种情况下将使用 GlobalRes_en_AU 的值。其他所有人将看到 GlobalRes 中的值。

4.2 PropertyResourceBundle

PropertyResourceBundle ResourceBundle 的子类,它从文本属性描述中读取资源列表。文本中包含键/资源对,形式为 key=value ,键和值都必须是字符串。 PropertyResourceBundle 对象从传递给其构造函数的 InputStream 中读取文本,并使用读取的信息构建一个查找表,以便高效访问。

前面描述的束搜索过程实际上还有一个额外的步骤,即在查找类 ResName 之后,会查找文件 ResName.properties 。例如,如果搜索过程找不到类 GlobalRes_eo_KI_left ,它将在查找下一个资源类之前查找文件 GlobalRes_eo_KI_left.properties 。如果该文件存在,则为其创建一个输入流,并用于构造一个 PropertyResourceBundle 对象,该对象将从文件中读取属性。

使用属性文件比创建 ListResourceBundle 的子类更容易,但文件有两个限制。首先,它们只能定义字符串资源,而 ListResourceBundle 可以定义任意对象。其次,属性文件的唯一合法字符编码是 ISO 8859 - 1 的字节格式。这意味着其他 Unicode 字符必须使用 \uxxxx 转义序列进行编码。

4.3 自定义 ResourceBundle 子类

对于大多数束, ListResourceBundle PropertyResourceBundle .properties 文件就足够了,但如果这些不够用,也可以创建自己的 ResourceBundle 子类。必须实现两个方法:

protected abstract Object handleGetObject(String key) throws MissingResourceException

返回与给定键关联的对象。如果该键在这个束中未定义,则返回 null ,这会导致 ResourceBundle 检查其父束(如果有)。除非自己检查父束而不是让束自动处理,否则不要抛出 MissingResourceException 异常。所有 “get” 方法都是基于这个方法编写的。

public abstract Enumeration getKeys()

返回该束理解的键的枚举,包括父束的所有键。

5. 练习

尝试让 GlobalHello 程序在示例区域设置下工作。使用 ListResourceBundle .properties 文件和自己的 ResourceBundle 特定子类添加更多区域设置。

通过以上内容,我们了解了在软件开发中如何进行国际化和本地化处理,包括区域设置的使用、资源束的概念和实现方式,以及如何通过这些技术让程序在不同的语言和文化环境中正常工作。希望这些知识能帮助开发者更好地开发出具有全球适用性的软件。

国际化与本地化开发指南(续)

6. 实际应用中的考虑

在实际开发中,进行国际化和本地化处理时,还需要考虑以下几个方面:

6.1 性能优化
  • 资源束加载 :资源束的加载可能会影响程序的性能,尤其是在频繁切换区域设置或加载大量资源时。可以考虑使用缓存机制,避免重复加载相同的资源束。例如,可以在程序启动时将常用的资源束加载到内存中,后续使用时直接从缓存中获取。
  • 查找效率 ListResourceBundle PropertyResourceBundle 内部都使用了查找表来提高资源查找的效率。在自定义 ResourceBundle 子类时,也应该优化查找逻辑,避免不必要的遍历和比较操作。
6.2 多线程环境
  • 线程安全 :在多线程环境中,需要确保资源束的访问是线程安全的。 ResourceBundle 类本身是线程安全的,但如果在自定义子类中进行了复杂的操作,可能需要考虑使用同步机制来保证线程安全。
  • 区域设置切换 :当多个线程同时进行区域设置切换时,可能会导致资源束加载和使用的混乱。可以通过使用锁机制或其他同步手段来确保区域设置的切换是有序的。
6.3 兼容性问题
  • 字符编码 :由于不同的操作系统和环境可能支持不同的字符编码,在处理国际化字符串时,需要确保使用统一的字符编码,如 UTF - 8。特别是在使用属性文件时,要注意其字符编码的限制。
  • 字体支持 :为了正确显示不同语言的字符,需要确保系统中安装了相应的字体。在开发过程中,可以进行字体测试,避免出现字符显示乱码的问题。
7. 总结与建议
7.1 总结
  • 区域设置 Locale 类是国际化和本地化的基础,它定义了不同地区的语言、文化和习俗。通过合理使用 Locale 对象,可以让程序根据用户的偏好进行本地化处理。
  • 资源束 :资源束机制是实现国际化的关键,它通过将字符串键映射到本地化资源,使得程序可以方便地在不同区域设置下使用不同的语言和资源。 ListResourceBundle PropertyResourceBundle 是常用的资源束实现方式,同时也可以自定义子类来满足特定需求。
7.2 建议
  • 提前规划 :在项目开发初期就应该考虑国际化和本地化的需求,设计合理的架构和数据结构,避免后期进行大规模的修改。
  • 使用常量 :将资源束的键字符串定义为常量,可以减少拼写错误,提高代码的可维护性。
  • 测试覆盖 :对不同的区域设置进行充分的测试,确保程序在各种语言和文化环境下都能正常工作。
8. 流程图示例

下面是一个资源束查找过程的 mermaid 流程图:

graph TD;
    A[开始查找资源束] --> B[指定束名称和区域设置];
    B --> C[查找完整名称的束];
    C -- 找到 --> D[使用该束];
    C -- 未找到 --> E[去掉最后一个组件,缩短名称];
    E --> F[重复查找];
    F -- 找到 --> D;
    F -- 未找到 --> G[检查是否指定了区域设置];
    G -- 是 --> H[使用默认区域设置的完整名称重新查找];
    H -- 找到 --> D;
    H -- 未找到 --> I[仅使用束名称查找];
    I -- 找到 --> D;
    I -- 未找到 --> J[抛出 MissingBundleException];
    G -- 否 --> I;
9. 表格总结
技术点 描述 优点 缺点
Locale 描述特定的地方,用于本地化行为 提供了统一的区域设置表示,方便程序根据不同地区进行调整 需要了解 ISO 代码,使用不当可能导致区域设置不准确
ListResourceBundle 将键列表映射到本地化对象 可以定义任意类型的资源对象,继承特性方便复用和扩展 需要编写代码实现 getContents 方法,对于简单的字符串资源可能略显繁琐
PropertyResourceBundle 从文本属性描述中读取资源列表 使用简单,易于维护 只能定义字符串资源,字符编码有一定限制
自定义 ResourceBundle 子类 满足特定的资源查找和处理需求 灵活性高,可以根据具体业务逻辑进行定制 需要实现 handleGetObject getKeys 方法,开发难度相对较大

通过以上的介绍和总结,我们对国际化和本地化开发有了更深入的了解。在实际开发中,需要根据具体的需求和场景选择合适的技术和方法,确保程序能够在全球范围内提供良好的用户体验。同时,不断实践和总结经验,提高自己在国际化和本地化开发方面的能力。

希望这些内容对开发者在进行国际化和本地化开发时有所帮助,让软件能够更好地适应不同的语言和文化环境,走向更广阔的市场。

【无线传感器】使用 MATLAB和 XBee连续监控温度传感器无线网络研究(Matlab代码实现)内容概要:本文围绕使用MATLAB和XBee技术实现温度传感器无线网络的连续监控展开研究,介绍了如何构建无线传感网络系统,并利用MATLAB进行数据采集、处理可视化分析。系统通过XBee模块实现传感器节点间的无线通信,实时传输温度数据至主机,MATLAB负责接收并处理数据,实现对环境温度的动态监测。文中详细阐述了硬件连接、通信协议配置、数据解析及软件编程实现过程,并提供了完整的MATLAB代码示例,便于读者复现和应用。该方案具有良好的扩展性和实用性,适用于远程环境监测场景。; 适合人群:具备一定MATLAB编程基础和无线通信基础知识的高校学生、科研人员及工程技术人员,尤其适合从事物联网、传感器网络相关项目开发的初学者中级开发者。; 使用场景及目标:①实现基于XBee的无线温度传感网络搭建;②掌握MATLAB无线模块的数据通信方法;③完成实时数据采集、处理可视化;④为环境监测、工业测控等实际应用场景提供技术参考。; 阅读建议:建议读者结合文中提供的MATLAB代码硬件连接图进行实践操作,先从简单的点对点通信入手,逐步扩展到多节点网络,同时可进一步探索数据滤波、异常检测、远程报警等功能的集成。
内容概要:本文系统讲解了边缘AI模型部署优化的完整流程,涵盖核心挑战(算力、功耗、实时性、资源限制)设计原则,详细对比主流边缘AI芯片平台(如ESP32-S3、RK3588、Jetson系列、Coral等)的性能参数适用场景,并以RK3588部署YOLOv8为例,演示从PyTorch模型导出、ONNX转换、RKNN量化到Tengine推理的全流程。文章重点介绍多维度优化策略,包括模型轻量化(结构选择、输入尺寸调整)、量化(INT8/FP16)、剪枝蒸馏、算子融合、批处理、硬件加速预处理及DVFS动态调频等,显著提升帧率并降低功耗。通过三个实战案例验证优化效果,最后提供常见问题解决方案未来技术趋势。; 适合人群:具备一定AI模型开发经验的工程师,尤其是从事边缘计算、嵌入式AI、计算机视觉应用研发的技术人员,工作年限建议1-5年;熟悉Python、C++及深度学习框架(如PyTorch、TensorFlow)者更佳。; 使用场景及目标:①在资源受限的边缘设备上高效部署AI模型;②实现高帧率低功耗的双重优化目标;③掌握从芯片选型、模型转换到系统级调优的全链路能力;④解决实际部署中的精度损失、内存溢出、NPU利用率低等问题。; 阅读建议:建议结合文中提供的代码实例工具链(如RKNN Toolkit、Tengine、TensorRT)动手实践,重点关注量化校准、模型压缩硬件协同优化环节,同时参考选型表格匹配具体应用场景,并利用功耗监测工具进行闭环调优。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值