4.2 插件模板与语言包的整合

模板放哪、怎么渲染,以及中文硬编码这个最大的坑怎么填。

4.2.1 插件的模板放在哪

插件页面也需要界面。X 系列的惯例是:脚本放 source/plugin/{标识}/,模板统一放它下面的 template/ 子目录,文件名和脚本同名,扩展名是 .htm 或 .php。

用途 位置
普通版模板 source/plugin/{标识}/template/xxx.htm
手机版模板 source/plugin/{标识}/template/mobile/xxx.htm

这里说的是「扩展项目」类型的模块——脚本以 .inc.php 结尾、模板放固定文件名那一类。「程序脚本」类型(.class.php)走页面嵌入,模板在模板套系里,不受这套规则约束。记住「同名」这条:脚本 myplugin.inc.php 对应的模板就得叫 myplugin.htm。系统按这个约定去找,找不到就白屏。

4.2.2 渲染:include template('xxx')

模板不能被浏览器直接访问,所以要用系统的模板函数渲染,插件脚本里最常见的一行是:

<?php
// source/plugin/你的标识/myplugin.inc.php
global $_G, $discuz;
$pluginid = 'myplugin';          // 与目录名一致

// ... 业务处理 ...

include template($pluginid);

template() 的参数就是不带路径和扩展名的标识。它会自动去 source/plugin/{标识}/template/ 下找同名文件,编译后输出。手机版单独指定:

include template($pluginid.'_mobile');

比 include template($pluginid) 更省事的是直接写标识字符串:include template('myplugin');。两种都对,$pluginid 变量的好处是脚本改名时只改一处。

改完模板记得更新缓存。模板不是每次都解析,先编译成 PHP 缓存到 data/cache/,缓存失效才重新编译——不更新缓存你会盯着没改动的页面怀疑人生。

4.2.3 模板里用什么标签

插件模板支持的是全套模板语言,和模板套系里的一致。下面这段可以直接放 template/myplugin.htm:

&lt;!-- source/plugin/你的标识/template/myplugin.htm --&gt;
&lt;div class="mp-wrap"&gt;
    &lt;h2 class="mp-title"&gt;{lang myplugin_title}&lt;/h2&gt;

    {if $count}
        &lt;p&gt;{lang myplugin_summary}({$count})&lt;/p&gt;
        {loop $list $item}
        &lt;li class="mp-item"&gt;
            &lt;a href="plugin.php?id=myplugin:detail&amp;id={$item[cid]}"&gt;{$item[title]}&lt;/a&gt;
            &lt;span class="mp-time"&gt;{$item[dateline]}&lt;/span&gt;
        &lt;/li&gt;
        {/loop}
    {else}
        &lt;p class="mp-empty"&gt;{lang myplugin_empty}&lt;/p&gt;
    {/if}
&lt;/div&gt;

{lang myplugin_title} 就是语言包调用,myplugin_title 是 key。注意 key 前面不带 lang_ 前缀——插件语言包文件里也不带,两边保持一致。

模板里的逻辑最终编译成 PHP,所以能写 PHP 语法。但别在模板里写业务查询——调试时要在编译后的缓存文件里翻 PHP 代码,排查成本高。查询放脚本里做,模板只负责显示。

4.2.4 插件语言包

插件语言包文件在开发阶段放 data/plugindata/{标识}.lang.php,安装时才被搬到插件目录,内容是一个 $lang 数组:

<?php
// data/plugindata/myplugin.lang.php
$lang = array(
    'title'   => '我的插件',
    'summary' => '当前共有',
    'empty'   => '暂无数据',
    'ok'      => '操作成功',
    'fail'    => '操作失败',
);

在脚本里读取就是 $lang['title'],模板里读取是 {lang myplugin_title}。脚本和模板共用同一份语言包,这也是为什么 key 不能带 lang_ 前缀——系统会自动拼。

语言包的价值在后台更明显:广告类、道具类这些挂载式扩展的 $name、$description 都填语言包 key,切语言时菜单名跟着变。硬编码中文的话,中文站看不出问题,一做英文站菜单就永远改不掉。

4.2.5 模板语言包:X5 的 i18n 扩展

X5.0 给模板加了语言包扩展能力,文件位置是 /template/i18n/SC_UTF8/lang_template.php:

<?php
// template/i18n/SC_UTF8/lang_template.php
$lang = [
    'nice' => '不错!',
];

这里可以写进 $lang 的内容比纯文案广:name、tplname、copyright、version,以及变量定义里的 var.title、var.description、var.extra——也就是模板列表页显示的那些文字。兼容扩展的路径是 /template/{标识}/i18n/en/lang_template.php,给某个具体套系补语言。

模板里用 {lang xxx} 调用。系统语言包在 source/language/ 下,模板语言包在 template/i18n/ 下,两个来源合并。

X5 的 default 套系默认启用 .php 后缀的模板文件。做多语言交付时要留意:官方允许同时兼容 X3.5 与 X5.0,但两种后缀并存时继承关系要逐一核对。

4.2.6 硬编码中文对照表

多语言站点里,硬编码中文是最大的坑——因为它编译进缓存后就不变了,改代码还容易漏掉散落各处的一两句话。

位置 硬编码写法 整改做法
模板静态文案 &lt;h2&gt;我的插件&lt;/h2&gt; {lang myplugin_title}
脚本提示语 showmessage('保存成功'); showmessage($lang['ok']);
后台设置标题 getsetting() 里 'label' => '推广标识' 改用语言包 key
挂载式扩展名称 var $name = '我的广告'; var $name = 'lang_promo_name';
校验失败提示 return '请选择对象'; 从语言包取,缺失时兜底

整改的实际做法是先写完功能,再全局搜一遍中文字符串,搬进语言包。搬完把语言包文件本身排除在搜索外——那是它该待的地方。

// 脚本里统一的取值写法,带兜底
function mp_lang($key, $default = '') {
    global $lang;
    return isset($lang[$key]) ? $lang[$key] : $default;
}

showmessage(mp_lang('fail', '操作失败'));

兜底参数别省。语言包漏发一个 key,页面直接白屏比显示英文更让人抓狂。

生活里给产品做多语言包装有个经验:先按一种语言把所有标签贴齐,再贴第二种,别一边做一边翻——翻出来的语言永远漏项。代码里的语言包就是同一个道理。

4.2.7 收尾检查

  • 模板在 source/plugin/{标识}/template/ 下,文件名与 .inc.php 脚本同名。
  • 手机版模板在 template/mobile/ 下单独一份。
  • 改完模板更新过缓存。
  • 脚本和模板都从语言包取值,没有硬编码中文。
  • 语言包 key 不带 lang_ 前缀,两边保持一致。
  • 取语言包值的地方有兜底默认值。

模板和语言包这条路走通,插件就能带着完整界面打包给别人。下一节换一个量级——不是扩展系统,而是替换系统的某一段逻辑。