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

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

Chris Yue 2 comments
Posts

昨天我们已经完成了首页和新闻列表页的外观改造,剩下的新闻详情页,就可以留给大家当作业自己实践了。今天我们要改造的是登录页。

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

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

模板重载

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

symfony screenshot time profiler button

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

symfony screenshot time profiler page

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

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

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

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

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

当然,我们可以顺手把“qq”变成大写的(使用 twig 的 upper filter,像这样 {{ 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。Symfony 翻译的顺序是先找“语言 + 区域”文件,再查找“仅语言”文件,再查找默认语言文件,至于默认语言文件如何设置,后面会提到。小提示:以后千万记得中文应该是 zh 而不是 cn,cn 指的是中国,而不是中文。

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

一如既往安装 Bundle

注册 Bundle

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

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

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

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

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

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

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

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

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

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

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

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

注册自定义Twig Filter

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

注意到我们加了一个名字为 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 目录下:

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

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

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

symfony screenshot jms translation bundle ui

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

最后,修改 parameters.yml 文件

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

symfony screenshot translation

里世界的第六天

Symfony 框架实战教程——第六天:模板重载与翻译 by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

如果觉得文章还不错,就请扫码鼓励一下作者吧
天使打赏人

2 Comments

ccna30

四月 3, 2015 在 12:49 下午

每次文章都能带给我们惊喜,尤其是楼主找的这些bundle,都是精品,请问应该到哪里去找bundle呢?

    Chris Yue

    四月 3, 2015 在 12:57 下午

    专门找还真找不着,这些都是在以前使用过程中各种尝试挑剩留下来的好东东

     

发表评论

− 3 = 2