Symfony2框架实战教程——第六天:模板重载与翻译

在未登录状态下点击“+发表新闻”按钮,也就是/news/new链接,会转跳到/oauth/login/链接,也就是第三方登录的链接(如果只实现了本地用户登录的同学,去的应该是另外一个界面,不过没关系,重载模板的原理都一样)。目前第三方登录页面只有一个可怜巴巴的“QQ”这个链接,让我们也给它加上页头页尾。

不过,这个模板文件很明显不是我们自己添加的,是第三方Bundle里自带的,难道我们要去修改第三方Bundle的代码吗?库文件随便修改这样好吗?当然不好,好消息是Symfony2框架实际是提供了框架模板重载机制的。

模板重载

在重载之前,我们需要知道模板的路径。那么如何找路径呢?很简单,只要先找到对应的路径对应的路由,然后找到对应的控制器,然后通过控制器代码就能知道加载的模板是……你以为我会跟你说这么傻的方法吗?其实你只需要点一下调试工具栏的这个像秒表的按钮:

是否看到某个.html.twig文件了呢?(友情提示:万一你电脑超级牛逼渲染模板连1ms都不到,是看不到模板渲染时间的,这个时候你可以将页面上方的Threshold改成0.1甚至更小)

得知模板的名称为HWIOAuthBundle:Connect:login.html.twig,我们就可以据此创建重载文件app/Resources/HWIOAuthBundle/views/Connect/login.html.twig。创建好了之后刷新一下登录页,因为我们什么都没写,如果重载成功,会返回一个空白页面。注意文件的命名规律,必须按此规律才能实现在app下的第三方模板重载

我们可以参考一下本身的模板代码,然后加上我们自己的base.html.twig的布局模板:

1

2

3

4

5

6

7

8

9

10

11

12

{# app/Resources/HWIOAuthBundle/views/Connect/login.html.twig %}

{% extends ‘base.html.twig‘ %}

{% block body %}

{% if error is defined and error %}

<span>{{ error }}</span>

{% endif %}

{% for owner in hwi_oauth_resource_owners() %}

<a href="{{ hwi_oauth_login_url(owner) }}">{{ owner | trans({}, ‘HWIOAuthBundle‘) }}</a> <br />

{% endfor %}

{% endblock body %}

光有一个链接其实依然很丑,我想把他做成一个带企鹅图标的按钮。我虽然不是设计师,但还好有开源素材给我用。fontawesome便是这么一个开源的图标库。它是以字体的方式存在的,这就意味着你可以不需要设计师做图,而是用CSS的方式让这些“图片”像字一样,随意变大,缩小,改变颜色,设置半透明效果,添加动画……只要CSS能做到的都能实现,而且因为字体是矢量的,所以放得再大也不会失真。

现在把fontawesome引入到项目中来:

1

2

3

4

5

{# app/Resources/views/base.html.twig #}

...

<link rel="stylesheet" href="//cdn.bootcss.com/font-awesome/4.3.0/css/font-awesome.min.css">

...

然后将之前的链接替换成创建按钮:

1

2

3

4

5

{# app/Resources/HWIOAuthBundle/views/Connect/login.html.twig #}

...

<a class="btn btn-default" href="{{ hwi_oauth_login_url(owner) }}">{{ owner | trans({}, ‘HWIOAuthBundle‘) }}</a>

...

当然,我们可以顺手把“qq”变成大写的(使用twig的upperfilter,像这样{{ owner | tarns({}, ‘HWIOAuthBundle‘) | upper }}),不过这么做并不好,因为要是以后添加了微博,那按钮岂不会变成WEIBO

翻译

其实HWIOAuth已经帮我们想到了这一点,所以用了一个叫trans的Twig filter。trans即translate即翻译的意思,我们如何实现翻译呢?首先我们需要一个文件,来定义翻译源和翻译结果的对应关系。比如目前我们的翻译源是qq,翻译结果是……为了效果明显点,就打算叫腾讯QQ吧。这样trans就会读取这个文件,如果发现翻译源是一个叫qq的字符串,他就会帮我们用腾讯qq来替换它(注意必须是只有qq的字符串,this is qq这样的字符串里的qq是不会被替换的)。

虽然翻译文件也可以有各种格式,比如最省事儿的YAML格式来做翻译,不过翻译也已经有业界标准,通常都是使用xliff格式。这个文件的存放位置,如果是第三方Bundle本身自带的翻译文件,放在XxxBundle/Resources/translations/yyy.zh_CN.xliff,但如果是项目自身的翻译,按官方的最佳实践的说法,放在app/Resources/translations/yyy.zh_CN.xliff。至于这个yyy具体是什么文件名,得看trans的第二个参数,比如这里是HWIOAuthBundle,那翻译文件名就是’HWIOAuthBundle.zh_CN.xliff’。如果没有第二个参数,默认是’messages’,对应文件名就是messages.zh_CN.xliff

使用xliff或者说用一种业界普遍使用的格式有一个最大的好处:相关的辅助软件多如牛毛,随便网上一搜就是一大把,不过这里我们先不用其他的软件来做翻译。

另外,文件名中间的zh_CN表示的是“中文-大陆”,也就是简体中文。语言代码一般由语言+区域的方式构成,比如en_USen_GB,但如果有些语句不严格区分区域的话,区域代码也是可以省略的,比如en。Symfony2翻译的顺序是先找“语言+区域”文件,再查找“仅语言”文件,再查找默认语言文件,至于默认语言文件如何设置,后面会提到。小提示:以后千万记得中文应该是zh而不是cn,cn指的是中国,而不是中文。

那么我们就来创建翻译文件吧,且慢!像翻译这种常见的需求……好吧不多说,接下来我们将用到简化翻译的Bundle:JMSTranslationBundle

一如既往安装Bundle

1

2

$ composer require jms/translation-bundle

注册Bundle

1

2

3

4

5

6

7

// in AppKernel::registerBundles()

$bundles = array(

// ...

new JMS\TranslationBundle\JMSTranslationBundle(),

// ...

);

然后我们就可以让JMSTranslationBundle自动帮我们创建xliff文件了

1

2

$ php app/console translation:extract zh_CN --dir=app/Resources --output-dir=app/Resources/translations

好嘞,让我们去看看生成文件:打开app/Resources/translations/……说好的xliff文件呢?为啥连根毛都没有啊?因为,模板文件里被翻译的是一个变量(那个owner),JMSTranslationBundle依然不知道翻译源是啥。

为了让JMSTranslationBundle知道有一待翻译短语叫qq,那我们就手工创建一个好了!(好吧其实我也 是为了演示JMSTranslationBundle的用法,文件完全可以自己创建,不过最好还是不要手写xliff,一是内容多,写起来麻烦,二是手写 容易出错)。在app/Resources/views下的随便什么模板文件,在某个block里输入下面的内容:

1

2

{{ ‘qq‘|trans({}, ‘HWIOAuthBundle‘) }}

然后再重新跑一次命令,我们便可以得到HWIOAuthBundle.zh_CN.xliff文件了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<?xml version="1.0" encoding="utf-8"?>

<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">

<file date="2015-04-01T01:13:48Z" source-language="en" target-language="zh_CN" datatype="plaintext" original="not.available">

<header>

<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>

<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

</header>

<body>

<trans-unit id="bed4eb698c6eeea7f1ddf5397d480d3f2c0fb938" resname="qq">

<jms:reference-file line="9">views/Connect/login.html.twig</jms:reference-file>

<source>qq</source>

<target state="new">qq</target>

</trans-unit>

</body>

</file>

</xliff>

到此,之前为了生成文件而随便添加的代码可以删除了。

这个文件如果不看xliff相关文档,你敢随便改吗?没关系,JMSTranslationBundle还有另外一个给力的功能:带Web UI的翻译工具。

因为在生产环境我们是不可能向公众开放翻译工具的,所以以下配置应该加到routing_dev.yml里

1

2

3

4

5

JMSTranslationBundle_ui:

resource: @JMSTranslationBundle/Controller/

type:     annotation

prefix:   /_trans

因为JMSTranslationBundle的WebUI使用了JMSDiExtra以及TwigExtensions两个库,我们也把它们加载进来:

1

2

3

$ composer require ‘jms/di-extra-bundle‘

$ composer require ‘twig/extensions‘

1

2

3

4

5

6

7

8

// in AppKernel::registerBundles()

$bundles = array(

// ...

new JMS\AopBundle\JMSAopBundle(),

new JMS\DiExtraBundle\JMSDiExtraBundle($this),

// ...

);

注意:因为TwigExtensions库不是Bundle,所以不需要进行注册工作。
另外JMSAopBundle是JMSDiExtraBundle需要的Bundle,虽然在安装过程中已经作为JMSDiExtraBundle的依赖自动被安装,但是注册还是需要我们手动的。

这个时候访问/_trans路径,还是会报错的,因为我们还有一些必要的设置没做,首先是
JMSDiExtraBundle的设置,这个Bundle作用其实就一个:可以用annotation的方式来注册Service。其实还是挺有用的,
大家如果有兴趣可以看看JMSTranslationBundle的TranslateController,看看它是如何用annotation的方式来注入一些服务到controller里的。其他不多说,先写配置:

1

2

3

4

5

jms_di_extra:

locations:

all_bundles: false

bundles: [JMSTranslationBundle]

另JMSTranslationBundle也需要通过配置文件得知一些信息,我们来将其配置上,比如我们要翻译成什么语言,在哪个路径里找翻译源,在哪个路径找翻译文件

1

2

3

4

5

6

jms_translation:

configs:

app:

dirs: [‘%kernel.root_dir%/Resources‘, ‘%kernel.root_dir%/../src‘]

output_dir: ‘%kernel.root_dir%/Resources/translations‘

设置好所有配置后,再次访问/_trans,虽然还有报错,但是报错不一样了。这一次是twig的错误,说是找不到truncate的twig filter。那我们如何去注册一个twig filter呢?

注册自定义Twig Filter

在定义Symfony的服务的时候,有些服务可能在Symfony初始化的时候,就需要被运行,而不是普通服务那样,调用它的时候才运行,比如
Twig的Filter服务,他需要在创建好服务对象之后,立马被Twig
Enviroment注册,这样才能在twig模板中使用定义好的filter。而要实现此种目的,Symfony的服务容器是通过给服务定义tag来实
现的。比如我们接下来要做的配置:

1

2

3

4

5

6

7

# app/config/services.yml

services:

twig.text_extension:

class: Twig_Extensions_Extension_Text

tags:

- name: twig.extension

注意到我们加了一个名字为twig.extension的tag,只要有此tag的服务,Symfony就会在服务创建好之后,立马将其注册到Twig Environment中。另外类似的还有Listener(tag名字为kernel.event_listener)等,只要初始化好服务,立马就被Event Dispatcher注册。

另外如果有兴趣,也可以看看Twig_Extension_Extension_Text是怎么定义Filter的。主要是getFilters里的new Twig_SimpleFilter(‘truncate‘, ‘twig_truncate_filter‘, array(‘needs_environment‘ => true)) 那句,其中`Twig_SimpleFilter构造函数的第二个参数可以是任意的callback,而callback的第一个参数,就是我们需要处理的字符串

回到正题,加上上面的配置之后,访问/_trans,就不会报错了,只不过样式根本就没有,一个连样式都没有功能还好意思发布成,你们信吗?当然不信。JMSTranslationBundle是自带了样式的,其实还包括JS文件。我们需要做的是将第三方Bundle中的这些静态文件,链接到web目录下:

1

2

$ php app/console assets:install --symlink

此命令将所有Bundle里的静态文件(位于XxxBundle/Resources/public),都创建了一个软链接(类似ln -s命令)到Web目录的bundle目录下。

有些系统可能不支持软链接,可以尝试去掉--symlink参数,直接复制到webbundles目录下。

我们再次刷新/_trans页面,可以看到这样的页面:

修改完中文翻译(没有确认按钮,只要输入框失去焦点就已经保存了),我们再刷新/oauth/login页面,按钮文字依然没有被翻译,这是因为我们还没有设置项目的默认语言,这需要在config.yml文件中指定:

1

2

3

4

5

6

7

8

9

10

11

framework:

...

# 注意以下设置的区别:

translator:

fallbacks: ["%locale%"] # 如果某个语言没有对应的翻译,就显示fallbacks里的语言翻译

default_locale: "%locale%" # 假如用户没有明确表示想要看哪个语言的页面

# 就认为当前用户是想看%locale%指定语言的页面

# 用户可通过HTTP请求中的`accept_language`

# 或者路由里的设置比如`domain.com/zh_CN/home`来指定,可以配置

最后,修改parameters.yml文件

1

2

locale: zh_CN

再刷新页面,翻译已经完成

时间: 2024-11-03 21:07:09

Symfony2框架实战教程——第六天:模板重载与翻译的相关文章

Symfony2框架实战教程——第六天#Alt:验证码

如果读者是从表世界过来的,应该知道怎么通过重载模板来改变第三方Bundle提供的模板.这里我们将重载用户登录页面作为作业留给大家,我们先来重载注册页面,不过为了提供新鲜的知识点,我们这次除了要装修注册页面以外,还要求给注册页面提供验证码. 对于我这种收集bundle的高手,自然少不了推荐验证码相关的bundle:GregwarCaptchaBundle 一如既往安装Bundle 1 2 $ composer require gregwar/captcha-bundle 一如既往注册Bundle

Symfony2框架实战教程——第五天:KnpMenuBundle创建菜单项+结合Twitter Boostrap3

做界面最好从交互比较多的页面入手,目前交互元素比较丰富的,有新闻列表页和新闻显示页. 为了开发快速,但也不会让界面太难看,我打算直接用Twitter Boostrap3了. 如果是看过我前几期文章的同学一定能猜得到,我一定又要说:“是的,像boostrap3这种常用的前端开发框架,相应的Bundle也是有的”.没错!这次我向大家推荐MopaBootstrapBundle,里面包含了使用LESS/SASS编译Boostrap CSS,支持让Symfony2框架以及常见Bundle比如我们的用过的K

Symfony2框架实战教程——第三天:用KnpPaginatorBundle实现翻页

创建业务数据模型 新闻数据算是我们业务模型里必不可少的模型之一.根据我们之前对需求的分析,我们可以很容易想到,新闻模型News需要的属性: 标题属性 文本属性 接下来,我们要在AppBundle里创建它,但是这些数据还需要一个持久层来保存数据,例如之前配置的Mysql.目前流行的开发方式,无论是Java还是ROR,都会使用ORM将数据库字段和类属性关联起来. Symfony2框架本身并不包含ORM工具(严格意义上来说,Symfony2框架,即FrameworkBundle,不包含ORM,安全组

Symfony2框架实战教程——第二天:创建新页面

在此之前,我们先明确一下我们要实现什么功能,在开始开展具体的代码工作前先明确自己的目标是一个节省时间的好习惯.我们的需求如下: 用户可以使用新浪微博或者QQ帐号登录. 用户可以投稿,需要填写的内容为“标题”+“正文” 用户可以对某篇文章进行评论 在以上的基础上,我们继续具体化我们的需求: 用户的内容都将使用markdown格式 评论内容不可超过140个字 新闻内容不可超过5000个字 标题内容不可超过70个字 好,到这里我们的需求已经很明确了,虽然简单得似乎不可能会有人用的样子- 修改配置文件

Symfony2框架实战教程——第一天:创建项目

Symfony2的教程非常的详尽,但是却不太亲近新手.新手想要完全理解Symfony2文档的内容,需要对互联网开发有一定工作经验才行,否则 总是有一种看不太明白,看完了也不不知道从哪儿开始的感觉.如果你有这种感觉,希望这篇文章能让你快速上手,本篇的目的,就是为了让Symfony2新 手,甚至是PHP新手能快速开始一个具体的项目. 本篇系列重点是为了带领新手使用Symfony2框架,如果您想了解的是Symfony2组件,可阅读我翻译Symfony2项目组老大的系列教程<使用Symfony2组件创建

Symfony2框架实战教程——第四天:用HWIOAuthBundle实现第三方登录

为了方便用户能快速发布内容,我们只用要求QQ的第三方登录就行了. 如果你打算跟着我一起完成这个项目,可能会因为QQ需要验证你是否有个人域名而卡在这一章,我写了一篇不用OAuth的.但本篇文章也最好看看,大部分知识点是完全一样的. 使用HWIOAuthBundle实现第三方登录 可以想象得到,第三方登录这种常见需求,相关的库也是有的.这里我再推荐一个:HWIOAuthBundle 如同昨日,我们先将它通过Composer引入到我们项目当中: 1 2 $ composer require "hwi/

Java之框架实战教程

开发快报: 页面打印功能,websocket 强制下线功能,玩转websocket技术  [金牌]获取[下载地址]   QQ: 313596790A 代码生成器(开发利器);     增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成   就不用写搬砖的代码了,生成的放到项目里,可以直接运行B 阿里巴巴数据库连接池druid;  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都

asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发4- 后台模板html页面创建

上一篇教程<asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发3-登录模块开发>完成了本项目的登录模块,登录后就需要进入后台管理首页了,需要准备一个后台模板,本文主要讲解如何创建这个后台模板,本文创建的后台模板不集成到项目内部,只是静态html页面.后台模板是系统开发必须的,一般小公司有个3套后台模板就够用了.本项目后台模板主要是jquery easyui插件创建的,不需要美工设计就可以创建出来,而且效果还可以,具体效果图如下: 这个版面的缺点是,只能适应2层

NDK-JNI实战教程(二) JNI官方中文资料

声明 设计概述 JNI接口函数和指针 加载和链接本地方法 解析本地方法名 本地方法的参数 引用Java对象 全局和局部引用 实现局部引用 访问Java对象 访问基本类型数组 访问域和方法 报告编程错误 Java异常 异常和错误代码 异步异常 异常的处理 JNI的类型和数据结构 基本类型 引用类型 域ID和方法ID 值类型 类型签名 UTF-8字符串 JNI函数 接口函数表 版本信息 调用实例方法 CalltypeMethod例程 CalltypeMethodA例程 CalltypeMethodV