Stripes tip

Stripes 专栏收录该内容
2 篇文章 0 订阅

Stripes简介

Stripes 同样是一种展示层框架,用于快速构建web程序;Stripes 完全抛弃了这些框架的弊病,使用了最新的 Java 5 带来的技术,遵循 “Convention over Configuration” 理念,只需要在Java 代码中加入少量的 Annotation,就可以完成配置,大量减少了代码的维护工作。

从 Stripes 网站,可以了解到 Stripes 框架的目标:

  • 简化 Java web 开发。

  • 针对一些常见问题,提供简单而强大的解决方案。

  • 容易上手,你很难想像让一个新 Stripes 的用户在30分钟内就能很快进入状态。

  • 容易扩展。

Stripes 提供的特性。

  • 零配置,不需要外部配置文件,这是 Stripes 最引人注目的特性。

  • 强大的binding引擎,足以应对复杂的对象。

  • 验证和类型转换机制非常容易使用和本地化。

  • 良好的本地化支持,甚至在 JSP 页面之间跳转时仍然生效。

  • 能够复用 ActionBean,将它作 view helper 使用。

  • 简易的 indexed property 支持。

  • 内置支持同一个 form 触发多个事件。

  • 具备透明的文件上传能力。

  • 支持增量开发。

  • 相当灵活,易于扩展。

    Stripes vs Struts 1

    作为一种经典的 MVC 框架,Struts 1 曾经在 Java 开发人员心目中占据了最高点。但是随着时间的推移,Struts 1 基础构架上先天性的缺陷显得越来越明显。同时,JSF 作为标准框架成为 Java EE 标准的一部分,Struts 1 面临着前所未有的挑战。一些开发人员开始尝试使用其它 web 框架,来替代 Struts 1Struts 1 的作者也曾尝试对 Struts 1 彻底改造,并提出了雄心勃勃的 “Struts Ti” 计划,但那就意味着 Struts 1 良好的向前兼容性将被打破,升级带来的代价非常大。所幸的是,一次机缘,让 Struts 1 和 WebWork 的开发人员能够坐下来,一起探讨 web 框架的未来。很快他们达成了共识,于是全新的 Struts 2 应运而生,它是基于 WebWork 的全新的 web 框架,新的 WebWork的开发工作也转移到了Struts 2上。

    和 WebWork 一样,Struts 1 作为一个遗留项目被保留下来。Struts 的另一个分支 Shale 从Struts 项目中剥离了出去,成为 Apache 下一个独立的项目。

    在 web 开发中,与 Struts 1 相比,Stripes 有很多闪亮的新特性。

    • 大大减少了开发工作量

      在 Struts 1 中,开发一个带有form的页面,你需要开发一个 JSP 页面文件,一个 ActionForm 类,一个 Action 类,并要在 struts-config.xml中注册你新建的 ActionForm 和 Action。

      而 Stripes 就要简单得多,你只需要创建一个 JSP 文件和 一个 ActionBean ,在 ActionBean 使用工 Annotation UrlBinding 指定你要响应的URL ,使用 Annotation HandlesEvent 将事件映射到方法上。

    • 增量开发

      在 Struts 1 中,当你写发一个 JSP 页面文件,其中有一个 <html:form> 标签,这时你还没有开发 FormBean ,当你把它部署到服务器,比如tomcat。打开浏览器,查看页面,它会抛出异常,提示找不相应的 FormBean

      而 Stripes 中,完全不用担心,即使没有创建 ActionBean,你一样可以预览 JSP 页面的效果,然后再创建 ActionBean

    • 属性绑定

      在 Struts 1 中,你的 Form 有一个嵌套(nested)属性 person.address.line1 ,如果 person 或 address 为 nullStruts 1 就会抛出异常,你必须在使用嵌套属性的对象中对其一一对预先初始化,这就是意味需要一些额外的工作。

      而 Stripes 中,完全不用担心没有初始化的问题,Stripes 会自动进行初始化,只要定义一个不带参数的修饰符为 public 的构造器。

    • Indexed 属性

      在 Struts 1 中,完全屈从于JavaBean 规范,但对 web 模型不是很友好。你必须实现indexecd getter 和 setter 方法。

      而 Stripes 中,你只要提供你所用的 List 或 Map 的 getter 和 setter 方法。使用泛型告诉 Stripes List 和 Map 中对象的类型。Stripes 会在必要的创建一个 List 或 Map 实例,需要进行扩展,产生新的对象,并将他们放入到 List 或 Map 中,设置好 List 中对象的属性。

    • Null的处理

      在 Struts 1 中,ActionForm 有一个 int 的属性,当时你保留页面字段为空时,Struts 1 会自动将转换成 0 ,更糟糕的,如果验证失败,回到 Form 页面,它会在输入框填充一个 0 。

      而 Stripes 中,如果页面输入框为空,那么这个字段不提交,ActionBean 不会填充字段值。

    • 格式化输出

      在 Struts 1 中,你只有使用 JSTL 提供的格式化功能 。

      而 Stripes 中,和 JSTL类似,提供了格式化标签。

    • 多事件支持的 Action

      在 Struts 1 中,你必须使用 DispatcherAction 来实现,并且所有 button 必须使用同一名称,不同的值,它会根据值的不同来调用的不同的方法,如果button 要进行国际化时,又是一件痛苦的事。

      而 Stripes 中,使用 button 的 name 属性本身来映射相应的方法。

    • JSP/View Helper

      在 Struts 1 中,没有什么好的方法将动态数据载入到 JSP 页面之中。

      而 Stripes 中,ActionBean 可以作为一个 View Hepler使用。

    • HTML 标签

      在 Struts 1 中, Struts 1 标签中 input 使用 property而不是 name ,class 被替换成 styleCalss

      而 Stripes 中,尽可能保持这些属性的名称与 HTML 一致。


      Stripes运行原理

      在 web.xml 我们注册一个 StripesFilter 和 DispatcherServlet 。在我们熟悉的 web 框架中,如 Struts 1 和 Struts 2,很少同时使用 Filter 和 Servlet 的。如在 Struts 1 中使用一个 ActionServlet 作为控制器,处理页面请求。在 Struts 2 中,使用了一个 Filter 处理所有请的请求。在 Stripes StripesFilter 主要是为请求处理作一些准备工作,它负责读取配置,处理 Locale ,并根据页面的 form 取得相应的 ServletWrapper 包装类。

      你可以也注意到 StripesFilter 同时会过滤所有的 JSP 文件和 DispatcherServlet。 如果请求的是一个 jsp 页面,如我们的首页 index.jsp ,在 StripesFilter 处理完后会直接跳转到相应的JSP页面。如果请求是一个 ActionBean 的某个 event ,后续处理工作会交给 DispatcherServlet 来进行。

      当 helloworld 运行起来之后,如果查看首页 index.jsp 的源代码,你会发现,在 stripes:form 标签中设置的beanclass="tutorial.action.HelloActionBean",转换了 action="/helloworld/Hello.action"Stripes 根据自己规则为你生成了 URL

      在 helloworld 程序中,index.jsp 提交时,DispatcherServlet 会根据 URL 来创建一个 ActionBean ,将 HttpServletRequest中的参数绑定ActionBean 的属性上。然后执行相应的 event 对应的方法。而这 event 的名称正是 index.jsp 中页面 form 中提交按钮的 name 属性值。

      使用ActionBean

      ActionBean 是 Stripes 的核心,本章介绍如何有效的使用 ActionBean,让 web 开发更加简单。

      在运行环境时,Stripes 会通过 setter 方法注入一个 ActionBeanContext 实例,你可以通过 getter 方法来取得一个ActionBeanContext 对象。通过 ActionBeanContext 你可以访问 HttpServletRequestHttpServletResponse 和ServletContext 对象。这里就可以看出,为了简化开发,一般情况下,Stripes 将它们隐藏起来,同时也提供了简单的方法,在需要的时候访问它们。

      在实际开发中,可以用一个公用类来访问来实现 ActionBean 接口,其它具体的 ActionBean 这个基类继承。

    处理请求事件

    Stripes 是一个基于 action 框架,但它也包含了一些基于事件的框架的概念。在 helloworld 实例中,页面中一个 FORM 是由一个 ActionBean负责进行处理,提交时会触发一个事件,submit 按钮的名称(sayHello)映射到 ActionBean 的某个方法,这个方法称为 event handler。

    下面是一个 Event Handler 的定义。

    public Resolution sayHello(){
    return new ForwardResolution("/greeting.jsp");
    }
    它是一个无参数的方法,返回一个 Resolution 。

    默认情况下,submit 按钮的 name 属性值就是相应的 ActionBean 的方法名。你可以在方法使用 Annotation HanlesEvent 来指定 ActionBean 的方法来处理那些事件。

    @HandlesEvent("sayHello")
    public Resolution greeting(){
    return new ForwardResolution("/greeting.jsp");
    }
    在上面的代码中,用 greeting 方法来处理 sayHello 事件请求。

    另外一个可能会用到 Annotation 是 DefaultHandler,当请求中没有提供相应的事件名称时,会执行带有 DefaultHandler Annotation 的方法。如果 ActionBean 只一个事件处理方法,那么这个方法就是默认的。

    URL 映射

    Stripes 提供一套有效的机制,根据 ActionBean 类名和 包名生成 URL 地址。它会将ActionBean 的包名转换成路径添加到 URL中,再添加ActionBean 类名,最后加一个后缀 .action。你可以会产生疑问,觉得 hellworld中生成的 URL 不符合这一规则。在URL处理时,为了使用URL 看起来更加简洁,Stripes 作了以下处理。

    • 将包名转换成路径,但是忽略包中有'web', 'www', 'stripes' 和 'action' 子包之前的部分,只截取其后的部分。
    • 在 URL 中添加类名,如果类名是以 Action ,Bean 或者是 ActionBean 结尾,去掉这些后缀。
    • 最后添加一个 .action 后缀,与 web.xml 中定义一致。

    现在你不会觉得奇怪了,tutorial.action.HelloActionBean的蝶变过程如下。

    • tutorial.action.HelloActionBean
    • /tutorial/Hello
    • /tutorial/Hello.cation

    这里我们忽略了前面的Servlet Context Path("/helloworld")。

    如果你不愿意使用它生成的URL ,可以自定义 URL,在 ActionBean 类上使用 Annotation UrlBinding,在参数中指定你的自定义的 URL

    @UrlBinding("/SayHello")
    public class HelloActionBean implements ActionBean{
    ...
    }
    重新运行这个程序,你可以发现生成的文件,已经替换成你的 URL

    处理表单

    当你提交一个表单时, Stripes 会进行以下处理。

  1. 首先会检查要求必填 (required) 的字段是否已经填充。
  2. 如果必填字段没有出现错误,则执行下面步骤。
    1. 执行最小/最大长度检测和模式匹配验证。
    2. 如果上一步没有错误出现,将字段值转换成相应的类型,并绑定到 ActionBean 的属性上。
    3. 如果类型转换过程没有出现错误,执行最小值/最大值检测(针对数值型)。
      • 执行自定义的检测方法。

      如果在验证和数据转换过程中出现错误,默认情况下  Stripes 会返回原页面,所有数据都会重新填充到表单中,并可以在页面显示错误信息。

      用户注册程序

      添加 required 约束

      在 RegisterActionBean 要求输入字段上加 @Validate(required=true)

      	@Validate(required=true)
      private String username;
      如果验证出错,Stripes 会默认返回原页面

      其它输入控制

      对于输入的信息,还可以添加字符长度范围,及字符匹配条件。如 username,password 要求输入6-20位字符,并且必须是数字或者字母。

      将 RegisterActionBean 中 usernamepassword上的Annotation 作出一点修改。

       	@Validate(required = true, minlength = 6, maxlength = 20)
      private String username;
      @Validate(required = true,  mask = "[0-9a-zA-Z]{6,20}")
      private String username;

      数据类型转换

      在输入数据正式绑定 ActionBean 之前,需要将它们转换成相应的类型。

      Stripes 内置了多个 TypeConverter。对于基本的数据类型,如byte,int,float,double,long,boolean 及其包装类 Byte,Integer,Float,Double,Long,Boolean 及 BigDecimal,BigInteger 都能自动转换成相应的类型。Date, Enum 等也有相应的TypeConverterDateTypeConverter 和 EnumeratedTypeConverter

      Email 地址合法性检测常常被认为应该属于 validation 范围,Stripes 利用 TypeConverter 来检测其合法性,显得有些牵强。Stripes 提供了一个 EmailTypeConverter ,实际它就是利用 JavaMail 的 API 检测地址的合法性。

      Stripes 还提供另外两个其它框架少有 TypeConverterPercentageTypeConverter 可以将百分比转换成实数。OneToManyTypeConverter 将字符串按分隔符转换成一个List。

      修改 RegiterActionBean , 在 email字段上的Validate Annotation 中使用 EmailTypeConverter 。另外追回两个字段,一个 java.util.Date类型的 birthDate ,另一个 boolean 类型的 subscriptionEnabled 。

      	@Validate(required = true, converter=EmailTypeConverter.class)
      private String email;

      @Validate(converter=DateTypeConverter.class)
      Date birthDate;

      @Validate(converter=BooleanTypeConverter.class)
      boolean subscriptionEnabled;

      在 StripesResources.properties中定义 Date 的格式。

      stripes.dateTypeConverter.formatStrings=yyyy MM dd

      自定义验证方法

      在表单输入数据转换完成后,就绑定到 ActionBean 的对应的字段上。

      在这个程序中,还有其它需要验证的地方。如,两次密码一致性,注册用户名的唯一性。

          @Validate(required = true, expression="this eq password")
      private String confirmPassword;

      一些验证需要与后台数据进行对比,比如检测用户名的唯一性。Stripes 提供了自定义的方法进行验证。

        @ValidationMethod(on="register")
      public void userExsited(ValidationErrors errors) {
      if("testuser".equals(username)){
      errors.add("username", new SimpleError("This username is taken , please select a different one."));
      }
      }

      输入控制ctd

      如果 ActionBean 的属性是一个普通的 Java 类,如何进行验证?Stripes 提供了 ValidateNestedProperties Annotation 解决这个问题。

      我们添加一个address字段,它是一个 Address类。

      	@ValidateNestedProperties({
      @Validate(field="zipcode", required=true),
      @Validate(field="addressLine1", required=true),
      @Validate(field="addressLine2", required=true)
      })
      private Address address;

      显示错误信息

      在 前面的例子中,我们都是显示全部错误信息。<stripes:errors/> 提供几个可选的属性action, beanclass, field, globalErrorsOnly。添加一个 action 或是 beanclass 可以指定显示哪个 form 的错误信息。设置field 参数可以精确指明哪一个输入字段的错误。globalErrorsOnly 可以设为 true 或 false ,表明是否显示全局错误。

      这里我们在表单上面显示全局错误,在username 字段上显示其详细信息。

      在userExsited 方法中添加一个 globalError。

      @ValidationMethod(on="register")
      public void userExsited(ValidationErrors errors) {
      if("testuser".equals(username)){
      errors.add("username", new SimpleError("This username is taken , please select a different one."));
      }
      if(!errors.isEmpty()){
      errors.addGlobalError(new SimpleError("Error occurs while saving data. Please fix it firstly."));
      }
      }

      如果你想完全自己定义错误信息,控制转向的页面。Stripes 提供了ValidationErrorHandler接口,它提供一个handleValidationErrors方法,你可以自己处理错误。

      		public class RegisterActionBean extends BaseActionBean implements ValidationErrorHandler {
      ...
      public Resolution handleValidationErrors(ValidationErrors errors) throws Exception {
      return new ForwardResolution("/error.jsp");

      }
      ...
      }

      这里如果出现错误,转向error.jsp。


      Resolution接口

      ActionBean 中一个事件对应的方法返回一个 Resolution对象。

      Resolution 接口定义。

      public interface Resolution {

      void execute(HttpServletRequest request, HttpServletResponse response)
      throws Exception;
      }

      Resolution 提供了 HttpServletRequest 和 HttpServletResponse 的访问能力。

      Stripes 提供了几种 Resolution 实现,ForwardResolution ,RedirectResolutionStreamingResolutionErrorResolution

      在前面例子中已经用到了ForwardResolution,它最终调用RequestDispatcher的forward方法显示目标页面。

      ForwardResolution 提供了几种构造方法,用于不同目的。

      public ForwardResolution(String path) {
      }
      public ForwardResolution(Class<? extends ActionBean> beanType) {
      }
      public ForwardResolution(Class<? extends ActionBean> beanType, String event) {
      }
      第一种,直接指定 URL 地址,这种方法简单明了。后两种不直接使用 URL ,它可以从一个ActionBean 转向另外一个 ActionBean ,Stripes 会自动转向目标 ActionBean 和触发 ActionBean 事件所对应的 URL 地址。

      RedirectResolutionForwardResolution不同的是它调用 HttpServletResponse 的sendRedirect方法显示目标页面。

      StreamingResolution 不会将客户端转向另一个页面,它向客户端发送数据流。这个Resolution 常常用于动态显示图片,显示图表,XML数据。它提供一个可选的 filename 属性,可以用于文件下载。如果设置了该属性,那么在输出时 Stripes 会在输出流中会在文件头写入 Content-Disposition 信息 ,它指定下载文件的名称,在浏览器中会自动弹出下载文件窗口。StreamingResolution 会在后介绍使用。

      ErrorResolution可以向客户端发送 HTTP 错误状态码和自定义的错误信息。

      另外,Stripes 还提供了一种 JavaScriptResolution ,但奇怪的是这个 Resolution 没有从 StreamingResolution 继承。

      RedirectResolution防止重复提交

      Struts 1  提供了一种简单的方法,来解决这个问题。在表单初始化时,设置一个隐藏的token值,提交时会比较token值。 Stripes  没有提供这一方法。

      使用 RedirectResolution

      使用 RedirectResolution 替代 ForwardResolution, 是最简单的方法。

      将 RegisterActionBean 中的 register方法修改成如下。

      public Resolution register() {
      return new RedirectResolution("/success.jsp");
      }

      另外还有一个问题就是,结果页面无法显示注册信息。

      很 多其它 web 框架提供了一个 Flash Scope来解决这个问题。Flash类似 Session,Request,它的生命周期比普通 Request 长,比 Session 短。Flash 通常也是基于 Session来实现,它跨越当前请求和下一个请求,保证了下一请求中还能读取当前请求中的属性,在下一请求结束后, Flash 就过期。Stripes 提供一个类似的实现,但是 Stripes 的实现的依赖 Session 过期。

      你可以把 ActionBean 放入 Flash scope中。

      public Resolution register() {
      return new RedirectResolution("/success.jsp").flash(this);
      }

      FlashScope

      Stripes 提供了一个 FlashScope 类,处理 Flash 状态。

      你可以将用下面的方法将 ActionBean 添加到 Flash Scope 中。

      FlashScope.getCurrent(getContext().getRequest(), true).put(this);			
      将其它对象放入 Flash Scope 也很简单。

      使用Captcha提高安全性

      Captcha是Java Completely Automated Public Test to tell Computers and Humans Apart的缩写。Captcha 为了每一次请求生成唯一的验证码,除了有效的防止数据重复提交外,还能够一些跨站的攻击。

      我们通过一个 ActionBean 显示验证码图片。

      public class CaptchaActionBean extends BaseActionBean {

      CaptchaManager 提供ImageCaptchaService 服务接口。

      public class CaptchaManager {
      在 RegisterActionBean 中验证是否一致,仍然使用 jCaptcha 提供的方法。

      Display Tag进行分页

      一些框架提供了专有分页显示方法,如 Apache Wicket,Apache Tapestry5。Stripes 没有提供相应的组件,来处理分页显示问题。很多开源的第三方框架提供了解决方案,比较著名的有 Display TagJMesa 。我在过去的项目用得最多的就是 Display Tag

      使用 Display Tag 处理分页

      首先,你需要从 Display Tag 下载最新的 Display TagDisplay Tag 依赖下面的文件。

      • commons-logging
      • commons-lang
      • commons-collections
      • commons-beanutils
      • log4j
      • itext(可选,用于导出 PDF 和 RTF )

      你可以从 Apache Commons 网站下载相应的包,并将相应的 jar 文件加入到项目的依赖库中。另外,Excel 导出需要添加displaytag-export-poi jar文件,它依赖pio jar,它是Apache Jakarta 下的一个子项目。这里不演示导出 PDF 和 Excel 文件。

      创建一个 Users 类,提供分页显示中的数据源。这里 Users是一个伪数据库访问类,后面会替换成真实的数据库环境。

      使用JMesa进行分页

      JMesa 提供了更多的特性,如结果集过滤等。它也提供了类似的 Display Tag 类似的 JSP 页面 taglib。

      从 JMesa 官方网站 下载,解压将 jmesa.jar 复制到项目的 lib 中。 另外 JMesa 依赖一些第三方的库。

      • commons-lang
      • commons-collections
      • commons-beanutils
      • slf4j

      多页面表单处理

      Struts 1 内置了多步提交表单的方案,在 ActionForm 添加一个 page 属性,指定当前是第几步,将 ActionForm 放入 Session 中,或者在下一步表单中使用隐藏属性存放其上一步的表单数据,保证多步表单数据不丢失。Stripes 也提供了一种有效的方式,来处理多步提交。
      Stripes 提供了 Wizard Annotation 保证多个页面的表单处理起来像在个页面一样。

      文件上传

      与普通的 Form 不同的是,文件上传的表单 form 标签必须添加一个 enctype="multipart/form-data" 属性,当使用标签 stripes:form 时它会自动添加这一属性。当表单提交后,表单数据以字节流的方式传递到远程服务器。如果自己分析上传表单内容,是件麻烦事。

      cos 和 commons-fileupload 是两种主流的上传工具,内置了表单分析方法。 Stripes 对他们进行了包装,不需要了解两种工具的上传操作的细节。提供了统一的接口,从一种实现切换到另一种实现,不需要修改任何代码。

      基于程序的向前的兼容性考虑,Stripes 自带了 cos ,你可以通过简单的配置决定使用哪一种后端实现。如果你想使用 commons-fileupload 后端来处理文件上传,在 Stripes Filter 上添加一个初始化参数。

      你可以从Apache Commons 上下载最新的 Commons FileUpload 。同时它还依赖其它 Commons包,你至少要添加一个 Commons IO。

      你可以通过另外一个参数 FileUpload.MaximumPostSize 来控制上传文件的大小,但必须注意的是这一参数控制的是整个上传表单的数据大小,而不是文件的大小。一般情况下,其它输入字段的体积相对较小,如果你允许上传体积为 1mb 以上的文件,这些体积几乎可以忽略不计。


      文件下载
      使用StreamingResolution 实现文件下载。
      当 StreamingResolution 设置了filename 属性时,会自动弹出下载文件确认框。

      页面布局

      一个web 项目常常包含很多页面,各页面常常只是内容不同。如果每个页面都是独立的,显然维护起来非常麻烦。JSP 提供了 <jsp:include> 可以将在 JSP 文件中包括其它 JSP 文件片断。但是每个特定的 JSP 页面都需要用 <jsp:include> 来插入相应的模板,这也是件麻烦的事。

      Struts 1 内置了 Tiles 支持,Tiles 能够有效的组织页面。现在 Tiles 已经 Apache 上一个一级项目,称为Tiles 2 ,相应的 Struts 1 中的Tiles 就是 Tiles 1。

      另外一种页面布局工具是 Sitemesh ,原理上与 Tiles 有很大差别,Sitemesh 对开发人员和设计人员更加友好,特定页面依然可以使用完整的HTML 标签,即可以包含 headbody等标签,运行时才与模板文件进行重新组合。

      Stripes 内置了一套简单的方案,与 Tiles 有些类似,但要简单很多,它只含三个相应的标签。

      • <stripes:layout-definition> 定义了一套可复用的页面模板。
      • <stripes:layout-component> 定义了一个模板页面的组件
      • <stripes:layout-render> 按模板生成页面

      首先创建模板文件,新建/layouts 目录,创建一个 JSP 文件,名为 default.jsp。这里假设把一个页面分为三部分,header,content,和 footer,实际应用中应该复杂得多。一般经常变化的就是内容,即content。

      <%@page contentType="text/html"%>
      <%@page pageEncoding="UTF-8"%>
      <%@taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
      <stripes:layout-definition>

      我们将header,footer 部分独立成一个单独文件,便于管理。这个layout模板定义了三个组件,即header, content, footer。

      /layouts/header.jsp/layouts/footer.jsp是普通的 JSP 文件片断。

      向layout模板文件传递参数

      嵌套使用


      国际化和本地化

      国际化常常被称为 i18n ,因为英文单词 internationalization 的开始字母 i 和结尾字母 n 之间有18 个字符。国际化的过程就是将自己的语言环境转换成其它语言环境的过程。本地化所做的工作则恰恰相反,它是将其它语言环境转换成本地语言环境。

      web 程序中的国际化和本地化牵涉到一些最基本的工作,不仅仅是字面上的翻译,还应该考虑各种语言中日期格式,时区,货币格式的转换等。


      ajax使用




    转载地址: http://blog.chinaunix.net/uid-191839-id-85854.html




    • 0
      点赞
    • 0
      评论
    • 0
      收藏
    • 扫一扫,分享海报

    参与评论 您还未登录,请先 登录 后发表或查看评论
    ©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

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

    余额充值