diff --git a/docs/source/tutorials/static-analysis.md b/docs/source/tutorials/static-analysis.md
index b8535f9d6..86c580a39 100644
--- a/docs/source/tutorials/static-analysis.md
+++ b/docs/source/tutorials/static-analysis.md
@@ -4,8 +4,12 @@ title: Static Template Analysis
{% since %}v10.20.0{% endsince %}
+{% note warn Experimental %}
+Note that this is an experimental feature and future APIs are subject to change. And internal structures returned can be changed w/o a major version bump.
+{% endnote %}
+
{% note info Sync and Async %}
-There are synchronous and asynchronous versions of each of the methods demonstrated on this page. See the [Liquid API](liquid-api) for a complete reference.
+There are synchronous and asynchronous versions of each of the methods demonstrated on this page. See the [Liquid API][liquid-api] for a complete reference.
{% endnote %}
## Variables
@@ -17,7 +21,7 @@ import { Liquid } from 'liquidjs'
const engine = new Liquid()
-const template = engine.parse(`\
+const template = engine.parse(`
{% assign title = user.title | capitalize %}
{{ title }} {{ user.first_name | default: user.name }} {{ user.last_name }}
@@ -129,7 +133,7 @@ By default, LiquidJS will try to load and analyze any included and rendered temp
```javascript
import { Liquid } from 'liquidjs'
-const footer = `\
+const footer = `
© {{ "now" | date: "%Y" }} {{ site_name }}
{{ site_description }}
@@ -137,7 +141,7 @@ const footer = `\
const engine = new Liquid({ templates: { footer } })
-const template = engine.parse(`\
+const template = engine.parse(`
Hi, {{ you | default: 'World' }}!
{% assign some = 'thing' %}
@@ -171,7 +175,7 @@ If an `{% include %}` tag uses a dynamic template name (one that can't be determ
### Advanced Usage
-The examples so far all use convenience methods of the `Liquid` class, intended to cover the most common use cases. Instead, you can work with [analysis results](static-analysis-interface) directly, which expose the row, column and file name for every occurrence of each variable.
+The examples so far all use convenience methods of the `Liquid` class, intended to cover the most common use cases. Instead, you can work with [analysis results][static-analysis-interface] directly, which expose the row, column and file name for every occurrence of each variable.
This is an example of an object returned from `Liquid.analyze()`, passing it the template from the [Partial Template](#partial-templates) section above.
@@ -238,7 +242,7 @@ The [`blockScope()`](/api/interfaces/Template.html#blockScope) method is respons
Whether a tag is an inline tag or a block tag, if it accepts arguments it should implement [`arguments()`](/api/interfaces/Template.html#arguments), which is responsible for returning the tag's arguments as a sequence of [`Value`](/api/classes/Value.html) instances or tokens of type [`ValueToken`](/api/types/ValueToken.html).
-This example demonstrates these methods for a block tag. See LiquidJS's [built-in tags](built-in) for more examples.
+This example demonstrates these methods for a block tag. See LiquidJS's [built-in tags][built-in] for more examples.
```javascript
import { Liquid, Tag, Hash } from 'liquidjs'
diff --git a/docs/source/zh-cn/tutorials/static-analysis.md b/docs/source/zh-cn/tutorials/static-analysis.md
new file mode 100644
index 000000000..ed6d39cd1
--- /dev/null
+++ b/docs/source/zh-cn/tutorials/static-analysis.md
@@ -0,0 +1,292 @@
+---
+title: 静态模板分析
+---
+
+{% since %}v10.20.0{% endsince %}
+
+{% note warn 实验性功能 %}
+这是一个实验性功能,未来的 API 可能会发生变化,返回的内部结构也可能在不进行主要版本更新的情况下更改。
+{% endnote %}
+
+{% note info 同步与异步 %}
+本文中的每种方法都提供了同步和异步版本。请参阅 [Liquid API][liquid-api] 了解完整的参考信息。
+{% endnote %}
+
+## 变量
+
+可以使用 `Liquid.variables(template)` 方法获取模板中使用的变量名称。它会返回一个字符串数组,每个字符串代表一个不同的变量,不包括其属性。
+
+```javascript
+import { Liquid } from 'liquidjs'
+
+const engine = new Liquid()
+
+const template = engine.parse(`
+
+ {% assign title = user.title | capitalize %}
+ {{ title }} {{ user.first_name | default: user.name }} {{ user.last_name }}
+ {% if user.address %}
+ {{ user.address.line1 }}
+ {% else %}
+ {{ user.email_addresses[0] }}
+ {% for email in user.email_addresses %}
+ - {{ email }}
+ {% endfor %}
+ {% endif %}
+ {{ a[b.c].d }}
+
+`)
+
+console.log(engine.variablesSync(template))
+```
+
+**输出**
+
+```javascript
+[ 'user', 'title', 'email', 'a', 'b' ]
+```
+
+可以看到,标签和过滤器参数中的变量也会包含在内,例如示例中的嵌套变量 `b`。
+另外,可以使用 `Liquid.fullVariables(template)` 方法获取包含其属性的完整变量列表。
+
+```javascript
+// 上例继续
+engine.fullVariables(template).then(console.log)
+```
+
+**输出**
+
+```javascript
+[
+ 'user.title',
+ 'user.first_name',
+ 'user.name',
+ 'user.last_name',
+ 'user.address',
+ 'user.address.line1',
+ 'user.email_addresses[0]',
+ 'user.email_addresses',
+ 'title',
+ 'email',
+ 'a[b.c].d',
+ 'b.c'
+]
+```
+
+或者,使用 `Liquid.variableSegments(template)` 获取每个变量路径的字符串和数字数组。
+
+```javascript
+// 上例继续
+engine.variableSegments(template).then(console.log)
+```
+
+**输出**
+
+```javascript
+[
+ [ 'user', 'title' ],
+ [ 'user', 'first_name' ],
+ [ 'user', 'name' ],
+ [ 'user', 'last_name' ],
+ [ 'user', 'address' ],
+ [ 'user', 'address', 'line1' ],
+ [ 'user', 'email_addresses', 0 ],
+ [ 'user', 'email_addresses' ],
+ [ 'title' ],
+ [ 'email' ],
+ [ 'a', [ 'b', 'c' ], 'd' ],
+ [ 'b', 'c' ]
+]
+```
+
+### 全局变量
+
+注意在上述示例中,`title` 和 `email` 被包含在结果中。通常你可能希望排除 `{% assign %}` 标签中定义的变量名,以及由 `{% for %}` 标签引入的临时变量。
+
+为了获取 _全局_ 变量(即由应用开发者提供,而不是模板作者定义的变量)的名称,可以使用 `globalVariables`、`globalFullVariables` 或 `globalVariableSegments` 方法(及其同步版本)。
+
+```javascript
+// 上例继续
+engine.globalVariableSegments(template).then(console.log)
+```
+
+**输出**
+
+```javascript
+[
+ [ 'user', 'title' ],
+ [ 'user', 'first_name' ],
+ [ 'user', 'name' ],
+ [ 'user', 'last_name' ],
+ [ 'user', 'address' ],
+ [ 'user', 'address', 'line1' ],
+ [ 'user', 'email_addresses', 0 ],
+ [ 'user', 'email_addresses' ],
+ [ 'a', [ 'b', 'c' ], 'd' ],
+ [ 'b', 'c' ]
+]
+```
+
+### 部分模板
+
+默认情况下,LiquidJS 还会尝试加载和分析任何被包含和渲染的模板。
+
+```javascript
+import { Liquid } from 'liquidjs'
+
+const footer = `
+`
+
+const engine = new Liquid({ templates: { footer } })
+
+const template = engine.parse(`
+
+ Hi, {{ you | default: 'World' }}!
+ {% assign some = 'thing' %}
+ {% include 'footer' %}
+
+`)
+
+engine.globalVariables(template).then(console.log)
+```
+
+**输出**
+
+```javascript
+[ 'you', 'site_name', 'site_description' ]
+```
+
+可以通过将 `partials` 选项设置为 `false` 来禁用部分模板的分析。
+
+```javascript
+// 上例继续
+engine.globalVariables(template, { partials: false }).then(console.log)
+```
+
+**输出**
+
+```javascript
+[ 'you' ]
+```
+
+如果 `{% include %}` 标签使用了动态模板名称(无法在渲染模板之前确定的模板名称),即使 `partials` 设置为 `true`,也会被忽略。
+
+### 高级用法
+
+上述示例使用的是 `Liquid` 类的便捷方法,适用于最常见的使用场景。
+如果需要更详细的信息,可以直接处理 [分析结果][static-analysis-interface],其中每个变量的每次出现都会记录行、列和文件名等信息。
+
+此处是对 [部分模板](#部分模板) 中模板进行 `Liquid.analyze()` 调用后返回的对象示例。
+
+```javascript
+{
+ variables: {
+ you: [
+ [String (Variable): 'you'] {
+ segments: [ 'you' ],
+ location: { row: 2, col: 14, file: undefined }
+ }
+ ],
+ site_name: [
+ [String (Variable): 'site_name'] {
+ segments: [ 'site_name' ],
+ location: { row: 2, col: 41, file: 'footer' }
+ }
+ ],
+ site_description: [
+ [String (Variable): 'site_description'] {
+ segments: [ 'site_description' ],
+ location: { row: 3, col: 9, file: 'footer' }
+ }
+ ]
+ },
+ globals: {
+ you: [
+ [String (Variable): 'you'] {
+ segments: [ 'you' ],
+ location: { row: 2, col: 14, file: undefined }
+ }
+ ],
+ site_name: [
+ [String (Variable): 'site_name'] {
+ segments: [ 'site_name' ],
+ location: { row: 2, col: 41, file: 'footer' }
+ }
+ ],
+ site_description: [
+ [String (Variable): 'site_description'] {
+ segments: [ 'site_description' ],
+ location: { row: 3, col: 9, file: 'footer' }
+ }
+ ]
+ },
+ locals: {
+ some: [
+ [String (Variable): 'some'] {
+ segments: [ 'some' ],
+ location: { row: 3, col: 13, file: undefined }
+ }
+ ]
+ }
+}
+```
+
+### 自定义标签的分析
+
+为了在静态分析中包含自定义标签的结果,这些标签必须实现 [Template 接口]( /api/interfaces/Template.html) 中定义的一些附加方法。LiquidJS 会使用这些方法返回的信息来遍历模板并报告变量使用情况。
+
+并非所有方法都是必须的,这取决于标签的类型。如果标签是一个块标签,具有起始标签、结束标签以及内容,那么它需要实现 [`children()`](/api/interfaces/Template.html#children) 方法。`children()` 需要返回一个生成器,这是为了像 `render()` 一样既可以同步也可以异步调用。该方法应返回当前标签的子节点,例如 HTML 内容、输出语句和标签。
+
+[`blockScope()`](/api/interfaces/Template.html#blockScope) 方法用于告知 LiquidJS 在标签块的持续时间内哪些名称会处于作用域中。这些名称可能依赖于标签的参数,也可能是固定的,例如 `{% for %}` 标签生成的 `forloop`。
+
+无论标签是行内标签还是块标签,如果它接受参数,则应实现 [`arguments()`](/api/interfaces/Template.html#arguments) 方法,该方法负责将标签的参数作为 [`Value`](/api/classes/Value.html) 实例或类型为 [`ValueToken`](/api/types/ValueToken.html) 的标记序列返回。
+
+以下示例展示了块标签如何实现这些方法。有关更多示例,请参见 LiquidJS 的[内置标签][built-in]。
+
+```javascript
+import { Liquid, Tag, Hash } from 'liquidjs'
+
+class ExampleTag extends Tag {
+ args
+ templates
+
+ constructor (token, remainTokens, liquid, parser) {
+ super(token, remainTokens, liquid)
+ this.args = new Hash(token.tokenizer)
+ this.templates = []
+
+ const stream = parser.parseStream(remainTokens)
+ .on('tag:endexample', () => { stream.stop() })
+ .on('template', (tpl) => this.templates.push(tpl))
+ .on('end', () => { throw new Error(`tag ${token.getText()} not closed`) })
+
+ stream.start()
+ }
+
+ * render (ctx, emitter) {
+ const scope = (yield this.args.render(ctx))
+ ctx.push(scope)
+ yield this.liquid.renderer.renderTemplates(this.templates, ctx, emitter)
+ ctx.pop()
+ }
+
+ * children () {
+ return this.templates
+ }
+
+ * arguments () {
+ yield * Object.values(this.args.hash).filter((el) => el !== undefined)
+ }
+
+ blockScope () {
+ return Object.keys(this.args.hash)
+ }
+}
+```
+
+[liquid-api]: /api/classes/Liquid.html
+[static-analysis-interface]: /api/interfaces/StaticAnalysis.html
+[built-in]: https://github.com/harttle/liquidjs/tree/master/src/tags
diff --git a/docs/themes/navy/languages/zh-cn.yml b/docs/themes/navy/languages/zh-cn.yml
index b4f53e702..cd2889a61 100644
--- a/docs/themes/navy/languages/zh-cn.yml
+++ b/docs/themes/navy/languages/zh-cn.yml
@@ -52,6 +52,7 @@ sidebar:
operators: 运算符
truth: 真和假
dos: DoS
+ static_analysis: 静态分析
miscellaneous: 其他
migration9: '迁移到 LiquidJS 9'