Facebook Twitter LinkedIn E-mail
magnify
Home Archive for category "Play Framework"

Play Framework Web开发教程(36): 多国语言支持

你所写的Web应用的用户可能来自不同的国家,他们使用不同的语言,已经使用不同的数字,日期等格式,Play提供了一些工具来帮助你支持应用的国际化。
不同的语言和格式规则成为”locale”,应用适应不同的”locale”称为国际化和本地化。国际化和本地化比较容易混淆,国际化指去除应用中和某些特定“locale”相关的代码。而本地化指构造某个特定locale的应用版本。对于一个支持国际化的应用来说,它应该支持多种可选的语言。在实际应用中,国际化和本地化是同时使用的。
本篇侧重介绍国际化应用中的静态部分(通常你在模板中hardcode的部分或是一些错误信息)。
配置message文件
在Play构造一个本地化的应用涉及如何书写message文件。比如,避免在应用中直接使用如”Log in”,”Thank you for your order”或”Email is required”等文字,我们可以定义一个message文件,对于不同的语言支持,我们分别构造对于的message文件:比如:

welcome = Welcome!
users.login = Log in
shop.thanks = Thank you for your order

这里我们可以看到如果使用message键值来替代实际显示的文字。 这里”.”没有特殊的意义,但可以帮助消息分组。

为了支持多国语言,我们需要在应用的配置文件中定义所要支持的语言,例如,我们修改application.conf文件,支持以下几种语言:

application.langs="en,en-US,nl"

支持的语言使用逗号隔开,支持的语言使用ISO 639-2 语言代码,加上可选的国家代码。比如上面的en-US代码美国英语。

然后,对于不同的语言,我们在conf目录构造对应的message文件,其命名规则为 messages.LANG ,其中LANG 使用具体的语言代码替代。比如法语fr,我们使用messages.fr文件名。
除此之外,我们创建一个不带后缀名的文件messages作为缺省语言,如果某个message没有翻译成所需的语言,那么就使用定义在messages文件中的定义。因此使用多国语言一般首先定义messages文件,确保定义完整的message。这样解释你忘记在某种语言的messages.LANG忘记定义某个键,那么还会显示缺省的文字。

在应用中使用message

在应用中使用message,你可以使用Messages的apply方法,例如:

Messages("users.login")(Lang("en"))

这个方法有两个参数列表,第一个参数列表为消息和消息参数,第二个参数列表为Lang参数,这个参数是一个implicit定义,通常Play应用提供了隐含的Lang定义,这个Lang定义基于机器缺省的locale定义。

Play 框架定义了由Request到Lang的隐含转换,这给我们带来很大的便利。如果在当前作用域有一个隐含的Request,那么就会有一个隐含的Lang定义(基于request的Accept-Language消息头)。比如你有如下的一个action方法:

def welcome() = Action { implicit request =>
  Ok(Messages("welcome"))
}

Play根据请求的消息头来决定使用哪种语言。如果消息头指明可以接受多种语言,那么Play就依次尝试这些语言,然后选取第一个可用的语言。如果消息头没有定义语言,那么Play选用application.conf定义的第一个语言。

同样,你可以在View模板中使用同样的语法来使用消息:

@()
<h1>@Messages("welcome")</h1>

要注意的是,如果你需要支持自动选取语言,那么你需要引入隐含的Request做为模板参数:

@(implicit request: Request)
<h1>@Messages("welcome")</h1>

此外,message 不仅仅支持前面定义的简单的文字串,也可以使用java.text.MessageFormat来格式化,也就是意味着你可以使用参数来定义字符串,例如:

validation.required={0} is required

然后再使用时,使用实际的文字来替换这些参数:

Messages("validation.required", "email")

再比如,你需要根据参数显示不同的信息,比如根据购物框中物品的数量显示不同的信息,可以定义如下的message:

shop.basketcount=Your cart {0,choice,0#is empty|1#has one item
|1< has {0} items}.

然后在应用中如果我们使用如下调用:

<p>@Messages("shop.basketcount", 0)</p>
<p>@Messages("shop.basketcount", 1)</p>
<p>@Messages("shop.basketcount", 10)</p>

那么实际显示:

Your cart is empty.
Your cart has one item.
Your cart has 10 items.
 

Play Framework Web开发教程(35): 使用LESS和CoffeeScript

浏览器最终处理的的HTML文档包含CSS和JavaScript,因此你的应用也必须输出这些浏览器支持的文件,但是使用CSS和JavaScript并不总是程序员最好的选择,很多程序员希望使用最新的Web技术,比如LESS和CoffeeScript。 LESS为一个stylesheet语言,通过LESS编译器可以把LESS语句翻译成CSS格式,同样CoffeeScrip通过编译也可以转换成JavaScript语言。
Play框架集成了LESS和CoffeeScript编译器,关于LESS和CoffeeScript的用法和优点不在本系列教材讨论的范围之列,本篇介绍如何在Play应用使用LESS和CoffeeScript。
如果你使用IntelliJ IDEA基础开发环境,LESS和CoffeeScript也会自动编译成对应的CSS和JavaScript,不需要你做额外的工作。
LESS
LESS(http://lesscss.org) 和CSS相比具有很多优点,比如LESS支持使用变量,minxin,嵌套,以及其它语言结构来帮助开发人员。比如下面的普通CSS,用来设置header,footer的背景色:

.header {
    background-color: #0b5c20;
}
.footer {
    background-color: #0b5c20;
}
.footer a {
    font-weight: bold;
}

这段CSS代码显示出来CSS的一些缺陷,我们必须重复定义background-color定义 ,而且需要重复定义.footer 如果需要定义footer内部的元素。 而如果使用LESS,上面代码可以改写为:

@green: #0b5c20;
.header {
  background-color: @green;
}
.footer {
  background-color: @green;
  a {
    font-weight: bold;
  }
}

我们可以定义一个变量@green,此外也可以嵌套定义。

coffeeScript
CoffeeScript(http://coffeescript.org) 和JavaScript相比在语法结构上有所增强.CoffeeScript是一套JavaScript的转译语言。受到Ruby、Python与Haskell等语言的启发,CoffeeScript增强了JavaScript的简洁性与可读性。此外,CoffeeScript也新增了更复杂的功能,例如列表内涵(List comprehension)、模式匹配(Pattern matching)等。一般来说,CoffeeScript可以在不影响执行效能的情况下,缩短约三分之一的程式码长度。
例如下面的JavaScript代码:

math = {
    root: Math.sqrt,
    square: square,
    cube: function(x) {
        return x * square(x);
    }
};

使用coffeescript改写成:

math =
  root: Math.sqrt
  square: square
  cube: (x) -> x * square x

关于LESS和CoffeeScript的中文教程,可以关注本博客,之后也会提供相关教程。

 

Play Framework Web开发教程(34): 使用implicit简化代码

关于隐式变换和隐式参数的教程可以参考

  1. Scala 专题教程-隐式变换和隐式参数(1):概述
  2. Scala 专题教程-隐式变换和隐式参数(2):使用implicits的一些规则
  3. Scala 专题教程-隐式变换和隐式参数(3):隐含类型转换
  4. Scala 专题教程-隐式变换和隐式参数(4):转换被方法调用的对象
  5. Scala 专题教程-隐式变换和隐式参数(5):隐含参数(一)
  6. Scala 专题教程-隐式变换和隐式参数(6):隐含参数(二)
  7. Scala 专题教程-隐式变换和隐式参数(7):View 限定
  8. Scala 专题教程-隐式变换和隐式参数(8):当有多个隐含转换可以选择时

 
我们还是继续前面的例子,现在我们假定我们需要支持购物车功能,在网站的每页的右上角显现当前购物车中物品的数目。如下图所示:
20141013001

由于我们需要在每页都显现购物车,因此我们可以在main.scala.html添加购物车部分的模板:

@(cart: Cart)(content: Html)
<html>
    <head>
        <title>Paper-clip web shop</title>
        <link href="@routes.Assets.at("stylesheets/main.css")"
        rel="stylesheet">
    </head>
    <body>
        <div id="header">
            <h1>Paper-clip web shop</h1>
            <div id="cartSummary">
                <p>@cart.productCount match {
                    case 0 => {
                        Your shopping cart is empty.
                    }
                    case n => {
                        You have @n items in your shopping cart.
                    }
                }</p>
            </div>
        </div>
        @navigation()
        <div id="content">
        @content
        </div>
        <div id="footer">
            <p>Copyright paperclips.example.com</p>
        </div>
    </body>
</html>

这个模板中,我们增加了一个Cart参数,它定义了一个productCount方法,我们使用模式匹配根据购物车中物品的数量显示不同的内容。
main模板中多了一个参数,因此我们在使用这个模板时,也需要多传入一个参数,这就意味着我们也要修改之前定义人catalog模板:

@(products: Seq[Product], cart: Cart)
@main(cart) {
    <h2>Catalog</h2>
    @views.html.products.tags.productlist(products)
}

定义的Acton方法也需要添加一个参数:

def catalog() = Action { request =>
    val products = ProductDAO.list
    Ok(views.html.shop.catalog(products, cart(request)))
  }
def cart(request: Request) = {
// Get cart from session
}

这里我们定义的cart方法可以从request中获取Cart的实例。
由于main模板多了一个参数,因此相关的所有Actor都需要修改来适应这个变化,随着项目的增大,工作量就越来越大,显得有些繁琐。 还好我们可以借助于Scala的隐含参数来解决这个问题。
借助于implicit,我们可以修改catalog模板如下:

@(products: Seq[Product])(implicit cart: Cart)

我们把第二个参数cart,作为单独的一个参数列表,并使用implict来修饰。因此如果当前作用域定义了隐含的Cart实例,我们可以忽略第二个参数来调用这个模板,这可以通过修改定义Controller来定义隐含参数:
20141013002

这里cart方法为一implict方法,其参数也是implicit类型。同样actor方法的参数类型也定义成implicit类型。 如果我们现在忽略Cart参数来调用catalog模板,Scala编译器会在当前作用域查找可用的Cart对象,这将找到cart方法,这个方法需要RequestHeader类型的参数,也是implicit类型,Scala编译器查找当前可以使用的Request对象,它找到了。

为了进一步实现代码可重用,我们可以把这部分代码抽取出来定义一个withCart trait:

trait WithCart {
  implicit def cart(implicit request: RequestHeader) = {
    // Get cart from session
  }
}

这里凡是需要使用cart的controller对可以通过mixin 这个trait来获取一个Cart的隐含定义。

注:如果你在Controller当前作用域定义一个implicit Request,那么在当前作用域自动获得RequestHeader,Session, Flash,和Lang的一个隐含定义,这是因为Controller类型定义这些转换的隐含转换。

 

Play Framework Web开发教程(33): 结构化页面-组合使用模板

和你编写代码类似,你编写的页面也可以由多个小的片段组合而成,这些小的片段本身也可以由更小的片段构成。这些小片段通常是可以在其它页面重复使用的:有些部分可以用在所有页面,而有些部分是某些页面特定的。本篇介绍如何使用这些可重用的小模板来构成整个页面。
Includes
到目前为止的例子,我们只显示了HTML的片段,没有实例显示整个页面。下面我们给出完整的显现产品列表的代码和模板:

def catalog() = Action {
	val products = ProductDAO.list
	Ok(views.html.shop.catalog(products))
}

对应的页面模板app/views/products/catalog.scala.html

@(products: Seq[Product])
<!DOCTYPE html>
<html>
    <head>
        <title>paperclips.example.com</title>
        <link href="@routes.Assets.at("stylesheets/main.css")"
        rel="stylesheet">
    </head>
    <body>
        <div id="header">
            <h1>Products</h1>
        </div>
        <div id="navigation">
            <ul>
                <li><a href="@routes.Application.home">Home</a></li>
                <li><a href="@routes.Shop.catalog">Products</a></li>
                <li><a href="@routes.Application.contact">Contact</a></li>
            </ul>
        </div>
        <div id="content">
            <h2>Products</h2>
            <ul class="products">
            @for(product <- products) {
                <li>
                    <h3>@product.name</h3>
                    <p class="description">@product.description</p>
                </li>
            }
            </ul>
        </div>
        <footer>
            <p>Copyright paperclips.example.com</p>
        </footer>
    </body>
</html>

这样我们就定义了一个完整的HTML页面,但是我们在其中添加了不少和显示产品列表不直接相关的标记,比如Catalog不需要知道菜单是如何显示的。页面模块化和重用性不高。
一般来说,一个action方法只应负责最终页面的内容部分。对于很多网站来说,页头,页脚,导航条在不同页面是通用的,如下图:

20141006001

在这个页面样式中,Header,Navigation,Footer通常是不变的,需要变化的部分是由Page Content指定的部分。

因此我们可以把之前产品列表页面模板中的导航条部分抽取出来单独定义一个navigation页面模板:
views/navigation.scala.html

@()
<div id="navigation">
    <ul>
        <li><a href="@routes.Application.home">Home</a></li>
        <li><a href="@routes.Shop.catalog">Catalog</a></li>
        <li><a href="@routes.Application.contact">Contact</a></li>
    </ul>
</div>

然后修改之前的catelog.scala.html

@(products: Seq[Product])
<!DOCTYPE html>
<html>
    <head>
        <title>paperclips.example.com</title>
        <link href="@routes.Assets.at("stylesheets/main.css")"
        rel="stylesheet">
    </head>
    <body>
        <div id="header">
            <h1>Products</h1>
        </div>
        @navigation()
        <div id="content">
            <h2>Products</h2>
            <ul class="products">
            @for(product <- products) {
                <li>
                    <h3>@product.name</h3>
                    <p class="description">@product.description</p>
                </li>
            }
            </ul>
        </div>
        <footer>
            <p>Copyright paperclips.example.com</p>
        </footer>
    </body>
</html>

这个修改使得我们的页面变得更好,因为Catalog页面无需再知道如何显示导航条,这种把部分页面模板抽取出来单独写成一个可重复使用页面模板的方法叫”includes”,而抽取出来的模板叫”include”。

Layouts
前面的include使得我们的页面模板变好了,但是还是有改进的余地。我们可以看到Catelog页面还是显示整个页面,比如HTML DocType,head等等,这部分不应该由Catalog来负责显示。前面页面模板只有div content部分由Catalog来显示:

<h2>Products</h2>
<ul class="products">
@for(product <- products) {
	<li>
		<h3>@product.name</h3>
		<p class="description">@product.description</p>
	</li>
}
</ul>

其它部分都应该放在Catalog 模板之外。我们也可以使用之前的include技术,但不是最理想的。如果我们使用”include”技术,那么我们需要另外两个新的模板,一个为Content前面部分的内容,另外一个模板为Content后面部分的内容。这种方法不是很好,因为这些内容应该是放在一起的。
幸运的是使用Scala的组合功能,Play支持抽出所有的内容到一个模板中,从catalog.scala.html 模板中抽出所有不应由catalog负责的部分,到一个布局模板:

<!DOCTYPE html>
<html>
    <head>
        <title>paperclips.example.com</title>
        <link href="@routes.Assets.at("stylesheets/main.css")"
        rel="stylesheet">
    </head>
    <body>
        <div id="header">
            <h1>Products</h1>
        </div>
        @navigation()
        <div id="content">
          // Content here
        </div>
        <footer>
            <p>Copyright paperclips.example.com</p>
        </footer>
    </body>
</html>

我们把这部分模板存放在app/views/main.scala.html中,要使得这个模板变成可以重用的,我们为它定义一个参数content,其类型为html,修改如下:

@(content: Html)
<!DOCTYPE html>
<html>
    <head>
        <title>paperclips.example.com</title>
        <link href="@routes.Assets.at("stylesheets/main.css")"
        rel="stylesheet">
    </head>
    <body>
        <div id="header">
            <h1>Products</h1>
        </div>
        @navigation
        <div id="content">
        @content
        </div>
        <footer>
            <p>Copyright paperclips.example.com</p>
        </footer>
    </body>
</html>

使用这个模板如同调用Scala函数类型,views.html.main(content) ,使用这个布局模板,我们修改catelog页面如下:

@(products: Seq[Product])
@main {
    <h2>Products</h2>
    <ul class="products">
    @for(product <- products) {
        <li>
            <h3>@product.name</h3>
            <p class="description">@product.description</p>
            }
    </ul>
}

如果有需要,我们可以为main布局模板添加更多的参数,比如将title也作为参数,
比如把

@(content: Html)
<html>
	<head>
	<title>Paper-clip web shop</title>

修改成

@(title: String)(content: Html)
<html>
	<head>
	<title>@title</title>

还可以给参数定义缺省值,比如:

@(title="paperclips.example.com")(content: Html)

这样修改可以进一步参数化布局模板,通用性更强。

 

Play Framework Web开发教程(32): 模板基本知识和一些通用结构

本篇介绍Play模板的一些基本用法,之后你可以编写View模板来实现MVC中的View部分。
@特殊符号
在Scala模板中,@符号表现Scala表达式的开始。和其它Web框架一些模板不同的是,Play的View模板没有使用特殊的符号作为表达式的结束符。Play的模板编译器自动根据上下文判断Scala表达式是否结束。这样可以使得编写View模板变得非常简洁:

Hello @name!
Your age is @user.age.

本例的第一行,name为一Scala表达式,第二行中,user.age也是表达式。而age后的”.”不是表达式的一部分。那么些吗的例子中:

Next year, your age will be @user.age + 1

Scala表达式也只到user.age为止,如果你需要表示比age大一年,那么需要使用括号:

Next year, your age will be @(user.age + 1)

有时,你需要使用多个Scala语句,那么需要使用{},比如:

Next year, your age will be
@{val ageNextYear = user.age + 1; ageNextYear}

在{}之间你可以使用任意的scala语句。
因为@是特殊字符,如果你需要输出@符号本身,那么你需要使用另外一个@进行转义,比如:

username@@example.com

模板使用@* *@作为注释,多条Scala表达式可以使用Scala语言的注释语法结构。 模板编译器不会在编译后的HTML文档中输出注释。
表达式
模板中Scala表达式可以使用任意的Scala语句,Play自动添加如下的API包到View模板中:

■ models._
■ controllers._
■ play.api.i18n._
■ play.api.mvc._
■ play.api.data._
■ views.%format%._

 
引入models._ 和controllers._ 可以保证在View模板中可以使用定义的Model和Controller类。 i18n定义的语言本地化和全球化支持。mvc._引入MVC相关的部件。data._定义了表单(form)和数据校验相关的类定义,而views.%format% 根据你定义的View模板格式而定,比如你使用html格式的模板,那么你引入views.html._,它定义了一些HTML辅助函数方便生成HTML。

显示集合类型数据
在Play中使用集合类型的地方非常多,比如显示用户列表,文章列表,产品目录,分类或者标签,比如我们可以使用下面的代码显示文章的名称:

<ul>
@articles.map { article =>
<li>@article.name</li>
}
</ul>

或者使用for-表达式(View编译器自动添加yeild关键字,因为没有yield,for表达式不会生成任何结果,因此你使用for表达式时可以省略掉yield部分)

<ul>
@for(article <- articles) {
	<li>@article.name</li>
}
</ul>

安全和转义
应用的开发者应该总是把安全放在心上,当处理View模板时,cross-site 脚本的安全漏洞要非常注意。 在允许用户输入的情况下,要注意HTML注入的风险,下图给出了一个使用cross-site 脚本攻击网站的示意图:

20140915001

因此我们需要使用HTML转义来避免这种情况。对于Play的View模板引擎来说,不是每个值都是同等对待的,比如对于字符串banana来说,如果我们需要在一个HTML文档中显示这个字符串,我们需要判断这是一个HTML代码片段,还是一个普通的字符串,如果是HTML代码片段,那么需要直接输出banana,如果是作为普通字符串来显示,那么需要对< / > 进行转义,因为它们是特殊字符串,那么我们需要输出:<b>banana</b>
有时你可能会觉得糊涂,究竟什么时候需要转义,对于Play来说,你在View模板中使用的字面量都被Play认为是HTML语句片段,不经转义直接输出。这是因为总是由开发人员来编写View模板,因此通常是认为安全的。但其中的Scala语句的输出都经过转义。比如我们由如下的View模板:

@(review: Review)
<h1>Review</h1>
<p>By: @review.author</p>
<p>@review.content</p>

我们使用如下代码来显示一个页面:

val review = Review("John Doe", "This article is <b>awesome!</b>")
Ok(views.html.basicconstructs.escaping(review))

这将显示如下页面:
20140915002

这正是我们所需要的,我们不希望用户输入的内容是HTML代码片段,也就是不允许用户输入使用HTML标记。

一般情况下,Scala语句部分输出是经过转义的,如果你需要阻止这种缺省行为 ,可以使用Html封装一下,此外你还可以使用Scala的XML函数库,直接输出HTML代码,例如:

@{
	<b>hello</b>
}

这里的hello也是没有转义的。

 

Play Framework Web开发教程(31): 使用View模板概述

前面介绍了MVC中的Controller 和Model部分,如果你不要Web页面(前台),那么我们的Play 教程就可以到此为止了,或者你也不想使用Play来生成HTML页面,而是使用其它Web前台技术,那么View部分你也可以不看了:-)。
不过大部分的Play应用都是需要使用Web页面的,对于HTML页面的显示,你可以使用Scala语句直接输出HTML,但是Play框架提供了更好的方法,-View模板引擎。使用View模板,你无需使用Scala语句直接生成HTML,你可以编写HTML文档,嵌入模板语言,可以大大提高工作效率:
通常在Controller中使用View模板,下图显示了Play中模板在HTTP请求-响应循环中的位置:
20140909001
使用View模板允许你重复使用部分HTML代码,比如页头,页脚,布局等。此外,使用View模板的另一个好处是可以把业务逻辑和现实逻辑分开。
Play模板引擎中使用Scala语言作为模板语言,因此支持数据类型安全,每个View模板最终都编译成Scala代码,在Controller使用View模板有如调用函数。