变量与多站点复用

把一个模板变成一个网络的,正是变量。变量设计对了,100 个站点从一个源渲染;设计错了,你就在把文字复制粘贴进每个预设。

三个来源,一张合并的查找表

渲染时,多数引擎把三处的变量合并成一张查找表。模板读到 %SomeName% 时,解析器查这张表并代入值。

  1. 模板局部助手——在模板体内用 #set#def 声明。
  2. 站点变量——按租户定义,一个站点一份,站点上的所有模板共享。
  3. 运行时变量——调用时传给解析器(文章上下文、系统上下文、用户上下文)。

创作决策归根到底就是:每条事实归哪一层管。

#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% ——文章级元数据

作者从不在模板里给它们赋值。读就够了。

变量优先级

同一个名字存在于多层时,优先级最高的赢。标准顺序,从强到弱:

  1. 运行时变量
  2. 站点变量
  3. 系统变量
  4. 模板局部 #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,无空格,无连字符。
  • 每个重复出现的变量都按重抽效应复查过。
  • 每个可选片段的空白都在分支内部处理好。
  • 每个后接连词的列表变量都查过分隔符冲突。
  • 五个渲染样本里没有残留的 %...%

准备好上结构了?下一篇讲排列实战——多样性真正住的地方。


继续本系列