变量与多站点复用
把一个模板变成一个网络的,正是变量。变量设计对了,100 个站点从一个源渲染;设计错了,你就在把文字复制粘贴进每个预设。
三个来源,一张合并的查找表
渲染时,多数引擎把三处的变量合并成一张查找表。模板读到 %SomeName% 时,解析器查这张表并代入值。
- 模板局部助手——在模板体内用
#set或#def声明。 - 站点变量——按租户定义,一个站点一份,站点上的所有模板共享。
- 运行时变量——调用时传给解析器(文章上下文、系统上下文、用户上下文)。
创作决策归根到底就是:每条事实归哪一层管。
用 #set 做模板局部助手
#set 用于单个模板内的短命助手:
#set %Lead% = {Welcome|Greetings|Hello}
%Lead% to %brand_name%!
适合的用法:
- 单模板助手——不提取就会让正文重复得难看;
- 同一模板里多次使用的长短语;
- 可读性——嵌套深到刺眼的时候。
不适合的用法:
- 租户专属的事实——那属于站点变量;
- 运行时已经提供的东西——局部
#set在优先级之争里必输。
容易绊倒人的语法规则
- 变量名不区分大小写。
- 只用 ASCII:字母、数字、下划线。没有空格,没有连字符。
#set只有在行首才生效。- 注释用
/# ... #/,处理前被剔除。 - 未知变量保持字面原样:
%MissingVar%渲染成%MissingVar%,不是空串也不是错误。把残留物当作 QA 失败。
站点变量——多站点倍增器
共享模板能服务许多站点、又不在每个域名上读起来一模一样,靠的就是站点变量。
一个通用的站点预设长这样:
#set %BrandTone% = {practical|no-nonsense|straightforward}
#set %Industry% = SaaS analytics
#set %TopFeatures% = [<minsize=3;maxsize=4;sep=", ";lastsep=" and ">dashboards|alerting|audit logs|SSO|role-based access]
#set %Audience% = {teams|product leads|operations}
现在每个共享模板都能读 %BrandTone%、%TopFeatures% 等等,输出随站点变化——没有人碰模板本身。
什么时候建站点变量
| 信号 | 动作 |
|---|---|
| 短语出现在 2 个以上模板里 | 提取成站点变量。 |
| 事实随站点变化 | 必须是站点变量。 |
| 列表要按站点打乱或不同 | 内含排列的站点变量。 |
| 只在一个模板里用一次 | 通常保持内联。 |
运行时变量
运行时变量来自调用方上下文:正在渲染的文章、当前用户、系统时钟。同名时它们覆盖站点变量和模板局部助手。
各引擎常见的运行时变量(名字取决于你的实现):
%year%——当前年份%lang%——当前语言代码%site_domain%——当前站点域名%brand_name%、%product_name%——文章谈论的品牌/产品%article_topic%、%category%——文章级元数据
作者从不在模板里给它们赋值。读就够了。
变量优先级
同一个名字存在于多层时,优先级最高的赢。标准顺序,从强到弱:
- 运行时变量
- 站点变量
- 系统变量
- 模板局部
#set
实际后果:运行时传了 %brand_name% 时,模板里的 #set %brand_name% = Demo 什么都不做。运行时赢。局部助手起名时别遮蔽运行时的名字。
命名约定
一个预设内部的一致性比任何具体风格都重要。尽管如此,一个合理的默认:
- 运行时变量:通常
lowercase_snake_case。它们不在你的控制之内。 - 站点变量:普通字符串用
PascalCase,语法变体用PascalCaseWithSuffix。 - 列表变量:用复数(
%TopFeatures%、%SupportedLanguages%)。 - 局部助手:短而达意——
%Lead%、%Closing%。
复合变量
站点变量可以互相引用。预设解析器先做变量间代换,同时让嵌套的 spintax 保持原样,这样后面的抽取仍然生效:
#set %FoundedLine% = launched in %FoundedYear%, based in %HQ%
#set %Pitch% = {fast|lightweight|self-hosted} %ProductCategory%
用复合变量把重复的事实拼装一次,跨模板复用。
重抽陷阱
这是新作者最常见的困惑来源。如果一个变量里装着原始 spintax,它的每次出现都独立重抽。
#set %Tone% = {safe|trusted}
%Tone% and %Tone%
可能的输出:
Safe and trusted
不要假定 #set 变量解析一次然后处处回显。需要两个不同的形容词,就用两个变量。
确实需要严格重复时:#def
上面的规则说的是 #set——它是宏。它的同胞 #def 形式相同,行为相反:值每次渲染只解析一次,同一个结果交给每处引用。
#def %Tone% = {safe|trusted|secure}
%Tone% and %Tone%
现在两个槽位永远一致——"safe and safe"、"trusted and trusted"——因为抽取发生在任何引用被填充之前,只有一次。两条指令的全部区别就在这里;其余(行首锚定、一行一条、从输出剔除、同样的命名规则)完全相同。
当一个值必须在整个模板里保持稳定时用 #def:喂给 {plural} 块的计数、从复数槽位提出来的名词、任何有意重复的短语。想要变化时用 #set——正文里这是常态。
有一条要明说的注意事项:#def 让单个变量与它自己一致。它不会让两个不同的变量相互关联——每个 #def 各自抽取,所以 %Noun% 和 %NounGenitive% 仍可能落在不同的词上。两个值必须相互一致时,把它们绑进一个枚举,而不是两个变量。
可选片段
枚举里的空分支就是可选片段:
{|official }website
{fast|secure|} withdrawals
片段可能消失时,把空格放进可选分支内部,否则会出现双空格或词粘连。想要可选的列表(可能为空的排列),把整个排列包起来:
{|[<minsize=2;maxsize=3;sep=", ";lastsep=" and ">Slack|Jira|Linear]}
引擎不能从排列里取零个。包一层是让“完全没有列表”成为可能结果的唯一办法。
分隔符冲突
常见的渲染 bug:列表变量里已经有 and,周围文本又加一个 and。
%Integrations% and other tools
如果 %Integrations% 解析成 Slack, Jira, and Linear,最终文本就是:
Slack, Jira, and Linear and other tools
修法:
- 插一个逗号:
%Integrations%, and other tools; - 改结构:
{Besides|Along with} %Integrations%, other tools...; - 去掉尾部连词,用冒号或破折号。
带 lastsep=" and " 的排列后面接以 and 开头的固定文本,问题相同。上线前先预览几个变体。
变量还是内联 spintax
| 用变量 | 用内联 spintax |
|---|---|
| 短语跨模板重复 | 一句话里的一次性同义词 |
| 事实随站点变化 | 普通动词或名词的同义词 |
| 列表按租户不同 | 固定的小型一次性列表 |
| 语法形式需要多种写法(见第 4 篇的俄语格变) | 只出现在一个语法位置的词 |
经验法则:先把重复的、语法敏感的短语提取成变量,再考虑零散的内联同义词槽。变量给你一个集中改错的地方;内联把错误撒得到处都是。
变量的常见错误
| 别这样 | 为什么 | 改成这样 |
|---|---|---|
| 把租户事实硬编码进共享模板 | 所有站点输出同样的文案,多站点复用作废。 | 把事实挪进站点变量。 |
用 #set 去覆盖运行时变量 | 运行时永远赢,你的覆盖悄悄失效。 | 给助手换名字,别遮蔽运行时的名字。 |
假定 %X% ... %X% 重复同一个词 | 每次出现重抽,可能得到两个不同的词。 | 重写句子,或用两个不同的变量。 |
| 假定缺失的变量会抛错 | 它们按字面渲染成 %MissingVar%。 | 加一道预览工序,标记残留的 %...%。 |
| 列表变量后面又拼一个 "and" | 产出 "A, B, and C and other things"。 | 用逗号,或改结构。 |
| 可选片段里忘了空格 | 产出双空格或词粘连。 | 把空格放进可选分支内部。 |
变量设计检查清单
- 每条租户专属事实住在站点变量里,不在共享模板里。
- 每条文章专属事实住在运行时变量里,不在
#set里。 - 没有任何
#set助手遮蔽运行时变量名。 - 变量名是 ASCII,无空格,无连字符。
- 每个重复出现的变量都按重抽效应复查过。
- 每个可选片段的空白都在分支内部处理好。
- 每个后接连词的列表变量都查过分隔符冲突。
- 五个渲染样本里没有残留的
%...%。
准备好上结构了?下一篇讲排列实战——多样性真正住的地方。