批处理文件国际化支持解决方案

背景

软件应用迅速发展扩张,逐步渗透到各行各业,越来越多的国家和地区认识到了计算机软件的重要性,大量的应用软件应用于日常的生活和工作当中。小到一个音乐播放软件,大到操作系统,都被广泛的应用。但是大量的计算机软件应用面临着一个非常严峻的问题,国际化问题。由于各个国家有不同的语言,而同一个国家的不同地区也有不同的语言习惯,例如中国大陆和台湾地区都使用中文,但是由于地区的语言等习惯不同,大陆地区使用简体中文,台湾地区使用的是繁体中文。对于一个应用软件要做到很好的本地化效果,就要充分考虑到用户的语言等习惯,例如 Windows Xp 系统包括中文简体 Xp 系统,中文繁体系统,英文系统等等。任何一个跨国家跨地域的软件都要碰到这样的问题,这个问题的解决和实现被称为国际化问题,所谓国际化就是 Internationalization,简称作 i18n 。

批处理脚本作为基于 Windows 直接支持的脚本,以其简洁、方便、快速而被广泛的直接应用或间接应用到大量的软件应用当中,然而作为一种通用的脚本语言没有提供一种成体系的多语言国际化支持,大大限制了这种简洁、方便的语言的使用,本文针对批处理脚本语言提出了一种有效的多语言支持解决方案。

Java 语言的国际化支持

Java 语言提供了 java.util.Locale 类进行多语言环境处理,Locale 对象表示了特定的地理、政治和文化地区。需要 Locale 来执行其任务的操作称为语言环境敏感的操作,它使用 Locale 为用户量身定制信息。例如,显示一个数值就是语言环境敏感的操作,应该根据用户的国家、地区或文化的风俗 / 传统来格式化该数值。使用此类中的构造方法来创建 Locale:

Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String variant) 
			

language 语言参数是一个有效的 ISO 语言代码。这些代码是由 ISO-639 定义的小写两字母代码。


表一 语言参数的 ISO 语言代码

English Name of Language All English Names All French Names ISO 639_2 ISO 639_1
ChineseChinesechinoischi/zhozh
EnglishEnglishanglaisengen
FrenchFrenchfrançaisfre/frafr

country 国家/地区参数是一个有效的 ISO 国家/地区代码。这些代码是由 ISO-3166 定义的大写两字母代码。


表二 语言参数的 ISO 国家/地区代码

Country names ISO 3166-1-alpha-2 code
CHINACN
TAIWAN, PROVINCE OF CHINATW
UNITED STATESUS
UNITED KINGDOMGB
FRANCEFR

根据 ISO 语言代码和国家/地区代码我们定位一个国家地区的语言习惯的时候可以使用“语言 - 国家”这样的格式,例如中国大陆的中文语言习惯可以表示为“ zh-CN ”,中国台湾的语言习惯可以表示为“ zh-TW ”等等以此类推。

Java 语言充分利用 Locale 对象对语言习惯进行处理和设计。基于 JAVA 语言的大部分应用充分利用这一特性进行多语言架构设计。例如 Struts 框架在进行多语言处理的时候结合 Web 应用的特点充分使用了 JAVA 的多语言特性,多语言处理按照如下逻辑,如果用户选择了某种语言习惯 Locale,则将该信息存储到该用户的会话当中,再次读取首先检查会话信息,然后将会话中语言习惯 Locale 映射为信息文件;如果会话信息不存在,则利用 Request 对象读取客户端浏览器的系统 Locale 信息,然后把这一信息解析,即可得到相应的语言习惯,并映射成相应的语言习惯文件中去。

批处理脚本语言国际化支持

批处理,也称为批处理脚本,英文译为 BATCH,批处理文件后缀 BAT 就取的前三个字母。是一种简化的脚本语言,它应用于 DOS 和 Windows 系统中,是由 DOS 或者 Windows 系统内嵌的命令解释器(通常是 COMMAND.COM 或者 CMD.EXE)解释运行,类似于 Unix 中的 Shell 脚本。

从某种意义上说,批处理就是一种编程,其实批处理是一种宏,但是用途非常广泛,可以实现一些用软件才可以实现的简单功能。它的构成没有固定格式,遵守以下规则:每一行可视为一个命令,每个命令里可以含多条子命令,从第一行开始执行,直到最后一行结束。其最简单的例子,是逐行书写在命令行中会用到的各种命令。更复杂的情况,需要使用 if,for,goto 等命令控制程序的运行过程,如同 C,Basic 等高级语言一样。如果需要实现更复杂的应用,利用外部程序是必要的,这包括系统本身提供的外部命令和第三方提供的工具或者软件。批处理程序虽然是在命令行环境中运行,但不仅仅能使用命令行软件,任何 32 位的 Windows 程序都可以放在批处理文件中运行。

批处理有很鲜明的特点:使用方便、灵活,功能强大,由于直接运行于 Windows 平台,不需要绑定其他编译器或者运行平台,可以很快速的定制易使用的轻量级应用。

在多语言支持方面,批处理脚本语言没有提供一套切实可行的解决方案体系。但是依赖于强大的 Windows 系统,批处理文件可以查询系统的系统变量,进而得出当前的系统状态和语言支持,因此可以通过建立一套有效的多语言支持解决方案解决多语言支持问题。

通用多语言解决方案灵活架构设计

概念层架构设计

依据面向对象分离可变的与不可变的思想理念,将支持多语言的应用系统划分为程序内核和多语言外壳层。在运行时刻,程序逻辑运行于程序内核之中,相对于多语言来说属于不变部分,运行时刻需要的语言按照系统和用户习惯要求动态调入程序内核,属于可变部分。在运行时刻的概念层体系架构为图 1 所示。


图 1. 运行时刻概念层体系结构
图 1. 运行时刻概念层体系结构

从复用和编程语言的特点来考虑,由于编程语言必须静态依赖,因此在设计期系统必须提供静态支持,如果直接依赖于某种语言则造成系统无法动态灵活支持多语言,因此在设计期增加多语言抽象层,程序内核依赖于多语言抽象层,从而支持多语言实现;从复用性的角度考虑,如果将多语言抽象层混杂到程序内核中实现,则会造成程序内核直接依赖于多语言实现功能,同时造成了多语言支持的功能复用性低,多语言的功能不能很好复用到其他软件系统。

据以上两点分析,最终多语言支持软件概念层体系结构如图 2 所示。


图 2. 概念层体系结构
图 2. 概念层体系结构

实现层架构设计

基于多语言支持软件的概念层体系结构设计,多语言支持软件实现层体系结构如图 3 所示,


图 3. 实现层体系结构
图 3. 实现层体系结构

依据概念层体系结构,实现层体系结构包括三层,程序内核层、多语言抽象层、多语言外壳。程序内核包装程序系统的业务逻辑和实现功能,位于实现层体系结构的最顶层,内核层由多个功能模块 (Program1 … ,m, … n) 构成直接依赖于多语言抽象层。多语言抽象层位于实现体系结构的中间层次,多语言抽象层一方面提供给程序内核层直接依赖,另一方面实现对多语言的动态调入支持。多语言抽象层通过变量键值 (Key) 的方式提供给内核层的直接依赖,以剥离内核层对多语言的直接依赖;多语言抽象层通过动态判断系统、用户语言习惯动态从多语言外壳层调入对应键值的语言,从而完成动态支持多语言。多语言外壳层位于实现层体系结构的最底层,提供多种语言的存储和 Key 值的映射对应关系,支持抽象层的多语言动态调入,常用的多语言存储方式包括数据库存储、文件存储等。

依据实现层的体系结构设计和运行时刻的多语言动态支持过程,多语言支持一般包括如下几个步骤:

  1. 获取系统、用户语言习惯。获取的方式很多,可以读取系统信息直接获取,也可以和用户交互,通过用户输入获取。
  2. 从软件系统中分离语言元素。分离语言元素实际上包括分离系统、用户习惯相关的元素,构建抽象层,使得软件系统程序内核依赖于抽象层。另一方面,从软件系统中剥离的语言元素首先选择合适的存储方式,然后根据对应的键值对相应的语言进行翻译,翻译后的语言按照既定的存储方式进行存储。一般常用的存储方式包括文件存储和数据库存储,两种存储方式各有优缺点。
  3. 从正确的存储方式中获取对应的多语言信息。利用在步骤一种获得的系统、用户语言习惯获得对应的语言种类,根据相应的存储方式,读取指定的键值对应的语言元素。

依据概念层的体系架构设计,步骤一和步骤三属于运行时刻实现,步骤二属于开发设计中的实现。

现过程中一般有两种角色,程序开发人员和多语言文案准备人员。程序开发人员的职责贯穿从步骤一到步骤三,多语言文安准备人员的职责主要集中在步骤二中。在实际的开发过程中开发人员首先完成从步骤一到步骤三的初期系统开发和单语种元素的剥离,并选定特定的语言种类。待基本完成了软件开发工作,软件系统处于稳定期后,多语言文案准备人员开始步骤二的多语言翻译,并按照选定的存储方式进行存储。

获取系统、用户语言习惯设计

获取系统、当前用户的语言习惯是多语言支持的第一步。目前常用的实现手段包括两种,通过读取系统参数获取系统当前的用户语言习惯;另一种方式是通过与当前用户进行交互,读取用户当前的输入内容和选择项,设定当前用户的语言习惯。对于操作系统语言习惯,常用的方式是直接读取操作系统的语言习惯信息,每次运行直接读取;对于当前用户的语言习惯,通过用户选定的语言习惯,软件系统记录该信息,并完成存储。

目前较为流行的软件系统包括 C/S 和 B/S 模式两种,C/S 模式直接作用于系统,可以直接操作系统的语言习惯信息,用户语言习惯则通过存储用户输入的信息,每次读取用户选择信息;对于 B/S 模式通过读取浏览器发送请求信息读取操作系统语言习惯,进而选定对应的语言习惯,对于用户语言习惯,通过将用户输入信息存储于服务器端,每次服务器端程序读取存储信息,然后解析发布。

分离软件系统中语言元素设计

该过程是一个设计期的静态行为,要解决多语言外壳层的语言存储问题和建立多语言抽象层,并根据多语言抽象层完成程序内核对于多语言抽象层的依赖。

多语言外壳层的语言存储方式一般包括数据库存储和文件存储两种方式,根据需要选择对应的存储方式。对于存储内容,一般存储 Key=Value 格式的内容,Key 为某一指定语言元素键值,Value 为对应的语言元素内容。

对于文件存储方式,要考虑到何种方式易于处理多语言。在程序处理中,能直接获得的是语言习惯,根据语言习惯和 Key 值得到某种语言的语言元素的过程是存储方式重要考虑的内容。一般采用每种语言一个语言文件,文件名用对应的语言习惯相对应,例如存储的多语言文件 message_en-us.properties,message_zh-cn.properties 等等。这种存储方式便于在选定某一语言习惯后,直接映射到对应的信息存储文件。对于数据库存储形式较多,从执行效率等角度有很多衡量考虑的内容,这里不予详述。

对于多语言抽象层,是一个位于程序内核和多语言外壳层的抽象层,该层次的目标为分离程序内核层对多语言外壳层的直接依赖,使程序内核层对多语言抽象层的直接依赖,保持了程序内核层动态对多语言支持过程中的稳定性;对于多语言外壳层,多语言抽象层实现了对于多语言外壳层语言元素的获取,因此多语言抽象层对多语言外壳层存在直接依赖,根据面向接口编程的思想可以有效的分离这种直接依赖。抽象层首先可以获得系统和用户语言习惯,得到了系统和用户语言习惯,然后提供了根据键值和语言习惯获取语言元素的方法。不考虑系统的其他品质,而仅从实现角度,该方法首先根据语言习惯映射出对应的语言文件,然后根据键值实现对于多语言外壳层数据的读取。

程序内核层对多语言抽象层的依赖。一般的对于这个提取语言元素的过程,要选定一种语言进行处理,以保证在系统运行和调试过程中不会出现语言元素不存在的错误。程序内核的语言元素提取过程如下,首先查找程序内核中所有的多语言信息,然后将每个语言元素提取出来放入对应的语言文件中,并选定一个固定的键值,最后用指定的键值和抽象层的语言元素读取方法来替换。

例子:

多语言存储方式选择文件存储,存储文件格式为 message_en-us.properties. 抽象层实现类为 Message,实现语言读取为 getMessage(String key) 方法。


清单 1. 抽象层的语言信息提取器

  1. publicStringgetMessage(Stringkey)
  2. {
  3. //获取语言习惯
  4. getLocale();
  5. //根据语言习惯获取对应的文件
  6. getMessgeFile();
  7. //读取对应的语言内容,并返回
  8. .....
  9. //
  10. returnmessage;
  11. }

假定程序内核的程序包括如下语言程序段 UI 为用户界面,UI.print(String str) 在用户界面上输出对应的内容。


清单 2. 未进行多语言处理前程序片断

  1. UI.print("系统检测开始");
  2. .......
  3. UI.print("系统检测图形界面是否正常");
  4. ......
  5. UI.print("系统检测完毕");

提取程序内核语言元素,使程序内核直接依赖于多语言抽象层。选定提供简体中文信息,把程序内核中语言元素提取到 message_zh-cn.properties 中。


清单 3. 保存内容到 message_zh-cn.properties 文件

  1. System_detect_begin=系统检测开始
  2. System_detect_GraphUI_normal=系统检测图形界面是否正常
  3. System_detect_over=系统检测完毕

替换程序内核语言元素,使之直接依赖于多语言抽象层。


清单 4. 进行多语言处理后程序片断

  1. UI.print(message.getMessage(System_detect_begin));
  2. .......
  3. UI.print(message.getMessage(System_detect_GraphUI_normal));
  4. ......
  5. UI.print(message.getMessage(System_detect_over));

到此,完成了从软件系统中的语言元素的提取,程序内核即可动态灵活的实现多语言支持。

获取正确的语言元素设计

该步骤一般为扩展步骤,根据不同的存储方式和读取存储目的完成不同的多语言构建层的实现。一般情况下,是直接对 2.4 中的 message.getMessage(String message) 方法的扩展,根据系统、用户语言习惯和键值获取对应语言文件的语言内容。在考虑到系统的灵活支持的前提下,通过 message 类可以采用桥模式的动态代理不同存储方式的读取,从而提供灵活存储方式读取的支持框架。


图 4. 基于桥模式的灵活多语言处理架构
图 4. 基于桥模式的灵活多语言处理架构

针对系统运行状态下,为了保证程序内核不依赖于某一种 Message 的实现手段,采用单例工厂模式来完成,从而保证系统动态的对用户语言习惯和系统语言习惯的灵活支持。


图 5. 基于单例和工厂模式的灵活多语言处理架构
图 5. 基于单例和工厂模式的灵活多语言处理架构

MessageFactory 的作用为根据系统选择创建一个 Message 对象,Message 对象负责具体的语言提取工作。

批处理支持多语言解决方案

基于第二章的通用多语言支持解决方案架构设计,本章提出了批处理多语言支持解决方案,该方案的目的是构建一个通用的轻量级多语言支持灵活解决方案。作为轻量级的应用,本文对 Batch 文件的多语言支持,采用文件存储的方式来完成,基本的实现过程按照获取本地语言,构建语言抽象层、提取语言因素和获取语言信息三个步骤来完成,完全采用脚本语言来实现,不依赖于其他第三方软件和 API 支持,以保持轻量级的易用,易移植等问题;作为通用的解决方案,本例构建的多语言支持可以快速被复用到其他批处理软件当中,进而降低多语言支持的工作量,提高多语言支持批处理软件的开发效率。

获取本地语言实现

当前用户语言习惯的读取通过跟用户直接交户来完成,这里不予赘述。对于系统语言习惯的读取,通过读取系统的参数来完成。


清单 5. getLocale.bat 内容
				
@echo off
for /f "skip=4 tokens=3" %%i in ('reg query "HKCU/Control Panel/International" /v
 "sLanguage"') do (
    set reg_localevar=%%i
)
call Keys_v2.bat %reg_localevar%,nlvmapping.txt,%~1


清单 6. nlvmapping.txt 内容
				
 DEU= de 
 ENU= en-us 
 ESN= es 
 FRA= fr 
 ITA= it 
 JPN= ja 
 KOR= ko 
 PTB= pt-br 
 CHS= zh-cn 
 CHT= zh-tw


清单 7. 调用 getLocale.bat 获取当前的语言习惯
				
getLocale.bat  varlocale

则 %temp_type% 和 varlocale 为当前系统的语言习惯,读取的语言习惯格式为标准的 en-us,zh-cn,zh-tw 等,并输出当前的语言习惯。

分离出程序中语言元素实现

建立信息文件的存储,考虑到轻量级批处理文件的多语言实现架构,采用基于文件的存储方式,有便于移植的特点。对于同一个语言采用一个语言文件进行存储。 message_en-us.properties,message_zh-cn.properties 等等。在程序处理过程中包括很多种类型的信息,为了减少信息的数量,提高信息的使用质量,一般根据信息的通用性和特殊性来分离。在程序处理过程中经常要使用的包括通用信息包括“是否”,“继续”,“退出”等等,有着较为普遍的通用性。在实际的信息抽取过程中,把通用性的要求用户行为的信息抽取出来可以有效提高信息复用性。键值的设定要比较具有代表性,例如“继续”可以设定为“ continue ”,“退出”为“ quit ”等等。

message_en-us.txt 信息文件存储的为英文信息


清单 8 message_en-us.txt 信息项内容
				
Success= Operation successfully! 
Failed= Operation failed, for more information please contact the administrator!”
UserCheckFailed= user Check failed!

构建语言元素抽象层,用以提高多语言支持基本架构,语言元素抽象层包括两大部分,抽象的语言元素键值和语言信息解析器。

创建多语言抽象层的语言信息解析器,getMessage.bat,传递参数直接调用即可。


清单 9. 调用 getMessage.bat 执行多语言处理
				
set value="" 
call getMessageFactory.bat key, value

key 为指定的键值调用后,则 value 值为某语言习惯的 key 对应的语言信息。

完成了语言元素抽象层的构建,程序内核依赖于该抽象层,与具体的语言显示功能彻底分离,保证了程序的稳定性同时能够灵活支持多语言。


清单 10 直接依赖于某种语言的程序片断
				
……………… .. 
echo “Operation failed, for more information please contact the administrator!”
………………… .


清单 11 基于语言元素抽象层的程序片断
				
……………… .. 
set failedvalue="" 
call getMessageFactory.bat "Failed",failedvalue 
echo %failedvalue% 
………………… .

获取正确的信息实现

信息读取器的作用是通过给定键值,可以把指定文件中的信息值取出来。根据第三章的灵活架构体系设计,可以实现较为灵活的多语言体系结构,批处理脚本语言属于非面向对象语言,因此在实现上基于面向过程语言的注射方式。常用的有动态读取和通过配置文件配置的方式,本文采用两种方式相结合提供灵活配置。另外,考虑到适用当前用户语言习惯的情况下,只是在与用户第一次交互后记录语言习惯,每次运行进行文件读取,较为简单,本也不予详述,对于读取系统语言习惯本文作详细的研究。

  1. 系统配置文件 sysconfig.txt 配置运行过程中的系统设置,对于动态支持多语言选择方式而言,可以动态配置 Message 的方式,动态 Message 实现的方式等。

    清单 12. sysconfig.txt 内容
    				
    getMessage= usergetMessage.bat 
    getMessageImp= DBgetMessageImp00000.bat

  2. 其中 getMessage 指定运行的 Message 方式行为的执行文件,getMessageImp 指定读取 Message 信息的各种存储方式的读取实现。
  3. getMessageFactory.bat 获取选定的 getMessage 方式,从系统中取,还是按照当前用户习惯取。

    清单 13 getMessageFactory.bat 内容
    				
    

    1. setvarimp=""
    2. callKeys_v2.batgetMessage,sysconfig.txt,varimp
    3. ifNot%varimp%==""gotoexecuteMessage
    4. For%%1in(*getMessage.bat)do(
    5. setvarimp=%%1
    6. gotoexecuteMessage
    7. )
    8. :executeMessage
    9. echo%varimp%
    10. call%varimp%%~1,%~2

    getMessageFactory.bat 首先读取系统配置的系统语言习惯行为,如果没有设定,则在当前目录下搜索相应的 Message 执行文件,如果搜索到了,则使用第一个执行文件进行执行。

  4. 获取系统当前语言习惯

    清单 14 sysgetMessage.bat 内容
    				
     

    1. setvarlocale=""
    2. setkey0=%~1
    3. setvarimp=""
    4. callgetLocale.batvarlocale
    5. callKeys_v2.batgetMessageImp,sysconfig.txt,varimp
    6. ifNot%varimp%==""gotoexecuteMessageImp
    7. For%%1in(*getMessageImp.bat)do(
    8. setvarimp=%%1
    9. gotoexecuteMessageImp
    10. )
    11. :executeMessageImp
    12. echo[%varimp%]
    13. call%varimp%%key0%,%varlocale%,%~2

    sysgetMessage.bat 采取了和 getMessageFactory.bat 的实现手段,首先从配置文件中读取多语言外壳的存储方式的执行脚本文件,如果没有读到则从当前目录中进行查找是否有可执行的文件,如果存在,则读取第一个进行执行。

  5. 采用文件存储多语言信息的实现:

    清单 15 filegetMessageImp.bat 内容
    				
     

    1. @Remsetvarlocale=""
    2. @RemgetLocale.batvarlocale
    3. @echooff
    4. setkey=%~1
    5. setvarlocale=%~2
    6. setfilename=message_%varlocale%.txt
    7. IFnotEXIST%filename%setfilename=message_en-us.txt
    8. callKeys_v2.bat%key%,%filename%,%~3

    filegetMessageImp.bat 实现了采用文件格式按照 message_en-us.properties,message_zh-cn.properties 方式存储的多语言解析读取。

  6. 基于 Key 值 Value 的对应关系的通用的实现:

    清单 16 Keys_v2.bat 内容
    				
     

    1. setkey=%~1
    2. setfile=%~2
    3. @Remecho%key%
    4. @Remecho%file%
    5. for/F"eol=;tokens=1,*delims="%%1in(%file%)do(
    6. @Remechokesy.bat--%%1
    7. if"%%1"=="%key%="(
    8. @Remecho%%2
    9. set"%~3=%%2"
    10. gotoend
    11. )
    12. )
    13. :end

    Keys_v2.bat 是一个通用的 Key=Value 格式文件内容读取器,有着较为通用的功能,在指定了满足格式要求的文件和键值的情况下,调用 Keys_v2.bat 返回指定的值。

快速构建多语言批处理系统

基于该多语言解决方案构架支持多语言批处理系统包括以下几个步骤:

  1. 按照 message_xx-xx.txt 格式,建立某种语言的信息文件以备进行测试和使用,比如 message_zh-cn.txt 。
  2. 在进行程序内核编写时,直接依赖于多语言解决方案系统。首先为要表达的语言元素准备一个 Key 值,然后通过对多语言解决方案系统和 Key 值的直接依赖,最后把准备好的 Key 值和语言元素写入对应的信息文件中。例如程序内核要使用:
    				echo “嗨,大家好”
    				

    依赖于多语言解决方案系统后,该程序片断如下,

    set  helloValue="" 
    call getMessageFactory.bat "hello", helloValue 
    echo %helloValue%

    并在信息文件 message_zh-cn.txt 中,加入

    Hello=嗨,大家好

  3. 配置 sysconfig.txt,准备运行调试。按照上文论述方式在系统配置文件中配置对应实现程序,如果不配置的话,则系统自动选择当前目录下对应的第一个执行文件。
  4. 翻译所有的 message 文件对应的文件。

结论与展望

本文针对多语言解决方案进行了较为详细的研究和探讨,主要包括几方面:

本文首先对于多语言支持的背景进行了一定的论述,包括对批处理文件和面向对象 Java 语言的国际化支持进行总结和分析,为通用多语言解决方案设计提供了可参考的依据。

本文给出了通用多语言系统的设计,并对设计的每一步进行了详细地论述和推理,以期完成一个功能上能够满足通用的灵活多语言支持系统,在系统架构上获得良好的可维护性和复用性,同时保持较为灵活的语言习惯和存储方式的切换使用。

在通用多语言系统的设计架构下完成了基于批处理脚本语言的程序实现,在保证了系统架构的灵活性前提下,完成了通用的多语言系统,基于该系统可以快速构建一个支持多语言的基于批处理的软件系统。

由于时间较为仓促,虽然实现了基于批处理脚本的多语言解决方案,但是批处理脚本并非面向对象语言,该解决方案的实现还存在不尽如人意的地方,需要进一步深化研究;另一方面,批处理脚本语言在实现手段上有其自身优势,该解决方案的实现在这方面没有采用批处理文件的这些特性,进一步采用这些特性可以有效的改善该解决方案执行性能等非功能属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值