mail-editor-pancake 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,6 +18,7 @@ Doc → Section → Column → Block // 仅四层,Block 不可再嵌套子
18
18
  - [MailEditor API 速览](#maileditor-api-速览)
19
19
  - [画布清空与重置](#画布清空与重置)
20
20
  - [界面主题与品牌色](#界面主题与品牌色)
21
+ - [国际化 i18n](#国际化-i18n)
21
22
  - [UI 选项 `ui`](#ui-选项-ui)
22
23
  - [构造选项(画布行为)](#构造选项画布行为)
23
24
  - [变量系统](#变量系统)
@@ -43,7 +44,7 @@ Doc → Section → Column → Block // 仅四层,Block 不可再嵌套子
43
44
  | 包名 | 内容 |
44
45
  |------|------|
45
46
  | `@simple-mail/core` | 编辑器引擎、`MailEditor`、类型与工具函数;样式入口 `@simple-mail/core/style.css` |
46
- | `@simple-mail/blocks` | 内置组件与示例自定义块(`builtinBlocks`、`allBlocks`) |
47
+ | `@simple-mail/blocks` | 内置组件与示例自定义块(`builtinBlocks`、`allBlocks`、`createBuiltinBlocks`、`createAllBlocks`) |
47
48
 
48
49
  本仓库在 Git 中目录名为 `simple-mail`;npm 上 monorepo 根包名为 `mail-editor-pancake`(因无 scope 的 `simple-mail` 已被占用)。**集成时请只安装并 import `@simple-mail/*`**,不要依赖 `mail-editor-pancake/packages/...` 这类深路径。
49
50
 
@@ -52,7 +53,7 @@ Doc → Section → Column → Block // 仅四层,Block 不可再嵌套子
52
53
  ```ts
53
54
  import { MailEditor } from '@simple-mail/core';
54
55
  import '@simple-mail/core/style.css';
55
- import { allBlocks } from '@simple-mail/blocks';
56
+ import { createAllBlocks } from '@simple-mail/blocks';
56
57
  ```
57
58
 
58
59
  仓库目录结构:
@@ -75,10 +76,11 @@ simple-mail/
75
76
  - **MJML 管线**:内部 `EmailDoc` JSON → MJML → 邮件客户端可用 HTML
76
77
  - 双模式:设计态 + 源码态(文档级只读 MJML/HTML;组件级可锁定 `lockedMjml`)
77
78
  - 内置块:文本、HTML(raw)、图片、按钮、分隔线、间距、Hero、社交组,以及 1/2/3 列布局;示例自定义:Logo、单链社交、页脚
78
- - **图片字段**:手输 URL;可选上传(`uploadImage`)、内置图库(`imageGallery`)、自管图床(`pickImageFromGallery`)
79
+ - **图片字段**:手输 URL;可选上传(`uploadImage`)、内置图库(`imageGallery`)、自管图库(`pickImageFromGallery`)
79
80
  - 撤销/重做、键盘删除、复制 Section/Block;**复制/导入设计稿**(JSON 信封)
80
81
  - **界面主题**:浅色 / 深色 / 跟随系统;画布仍为白纸贴近成品
81
82
  - **品牌色**:`accentColor` / `setAccentColor`;可选顶栏拾色器
83
+ - **国际化**:内置 `zh-CN` / `en-US`;支持 `locale`、`messages` 覆盖;blocks 可按语言生成
82
84
  - **仅搭正文**:`ui.hideMailMeta` 隐藏主题、Preheader 与顶栏「邮件设置」
83
85
  - **清空 / 重置**:`presetDoc` 与 `initialDoc` 分离
84
86
  - **变量系统**:`setVariables`;`kind: link | image`;顶栏 `{{ }}` 弹层
@@ -119,6 +121,13 @@ pnpm --filter @simple-mail/playground-vanilla build
119
121
 
120
122
  若你正在本地修改本仓库源码,请在 monorepo 根执行 `pnpm build` 后再刷新引用方项目,以保证 `dist` 与类型声明一致。
121
123
 
124
+ Playground 支持通过 URL 验证语言:
125
+
126
+ ```text
127
+ http://localhost:5173/?locale=zh-CN
128
+ http://localhost:5173/?locale=en-US
129
+ ```
130
+
122
131
  ---
123
132
 
124
133
  ## 安装与集成
@@ -135,10 +144,16 @@ pnpm add codemirror mjml-browser sortablejs
135
144
  ```ts
136
145
  import { MailEditor } from '@simple-mail/core';
137
146
  import '@simple-mail/core/style.css';
138
- import { builtinBlocks, allBlocks } from '@simple-mail/blocks';
147
+ import {
148
+ builtinBlocks,
149
+ allBlocks,
150
+ createBuiltinBlocks,
151
+ createAllBlocks,
152
+ } from '@simple-mail/blocks';
139
153
  ```
140
154
 
141
155
  - 仅需内置块时用 `builtinBlocks`;需要 Logo/页脚等示例时用 `allBlocks`,或与业务 `defineBlock` 合并。
156
+ - 需要英文或动态语言时,用 `createBuiltinBlocks({ locale })` / `createAllBlocks({ locale })` 生成块定义。
142
157
  - `@simple-mail/blocks` 依赖 `@simple-mail/core`,版本宜对齐。
143
158
 
144
159
  ### 本地 link(参与本仓库开发时)
@@ -203,11 +218,14 @@ npm 上仅发布 `@simple-mail/core` 与 `@simple-mail/blocks`。根包 `mail-ed
203
218
  ```ts
204
219
  import { MailEditor } from '@simple-mail/core';
205
220
  import '@simple-mail/core/style.css';
206
- import { allBlocks } from '@simple-mail/blocks';
221
+ import { createAllBlocks } from '@simple-mail/blocks';
222
+
223
+ const locale = 'zh-CN';
207
224
 
208
225
  const editor = new MailEditor({
209
226
  container: document.getElementById('app')!,
210
- blocks: allBlocks,
227
+ locale,
228
+ blocks: createAllBlocks({ locale }),
211
229
  initialDoc: {
212
230
  meta: { subject: '欢迎', width: 600 },
213
231
  variables: [{ key: 'user.name', label: '用户名', sample: '张三' }],
@@ -268,7 +286,8 @@ editor.setVariables([
268
286
  ```ts
269
287
  const editor = new MailEditor({
270
288
  container: el,
271
- blocks: allBlocks,
289
+ locale: 'zh-CN',
290
+ blocks: createAllBlocks({ locale: 'zh-CN' }),
272
291
  initialDoc: loadedFromApi,
273
292
  presetDoc: businessDefaultTemplate,
274
293
  onChange: (doc) => save(doc),
@@ -297,6 +316,128 @@ editor.getTheme();
297
316
 
298
317
  ---
299
318
 
319
+ ## 国际化 i18n
320
+
321
+ 编辑器内置 `zh-CN` 与 `en-US`,默认 `zh-CN`。国际化覆盖范围包括编辑器 UI、弹窗、图库、富文本工具条、内置 blocks 与示例自定义 blocks 的名称、属性、默认文案。
322
+
323
+ ### 基础用法
324
+
325
+ ```ts
326
+ import { MailEditor, type SimpleMailLocale } from '@simple-mail/core';
327
+ import { createAllBlocks } from '@simple-mail/blocks';
328
+
329
+ const locale: SimpleMailLocale = appLanguage === 'en' ? 'en-US' : 'zh-CN';
330
+
331
+ const editor = new MailEditor({
332
+ container,
333
+ locale,
334
+ blocks: createAllBlocks({ locale }),
335
+ });
336
+ ```
337
+
338
+ 仅使用内置块:
339
+
340
+ ```ts
341
+ import { createBuiltinBlocks } from '@simple-mail/blocks';
342
+
343
+ new MailEditor({
344
+ container,
345
+ locale: 'en-US',
346
+ blocks: createBuiltinBlocks({ locale: 'en-US' }),
347
+ });
348
+ ```
349
+
350
+ `builtinBlocks` / `allBlocks` 仍保留,默认中文,主要用于兼容旧集成;新项目建议使用 `createBuiltinBlocks` / `createAllBlocks`。
351
+
352
+ ### 覆盖文案
353
+
354
+ 可用 `messages` 覆盖内置文案。只需传入要覆盖的 key,未传的 key 会回退到对应语言,再回退到中文。
355
+
356
+ ```ts
357
+ new MailEditor({
358
+ container,
359
+ locale: 'en-US',
360
+ blocks: createAllBlocks({ locale: 'en-US' }),
361
+ messages: {
362
+ 'topbar.exportHtml': 'Export final HTML',
363
+ 'leftPanel.components': 'Content blocks',
364
+ },
365
+ });
366
+ ```
367
+
368
+ 可导入类型与内置字典:
369
+
370
+ ```ts
371
+ import {
372
+ createI18nContext,
373
+ enUSMessages,
374
+ zhCNMessages,
375
+ type SimpleMailLocale,
376
+ type SimpleMailMessagesInput,
377
+ type SimpleMailT,
378
+ } from '@simple-mail/core';
379
+ ```
380
+
381
+ ### 宿主语言切换
382
+
383
+ `MailEditor` 构造后不会热更新 `locale` / `blocks`。宿主应用切换语言时,建议保存当前文档、销毁旧实例、用新语言重建。
384
+
385
+ ```ts
386
+ let editor: MailEditor | null = null;
387
+
388
+ function mount(locale: SimpleMailLocale, initialDoc?: Partial<EmailDoc>) {
389
+ editor = new MailEditor({
390
+ container,
391
+ locale,
392
+ blocks: createAllBlocks({ locale }),
393
+ initialDoc,
394
+ onChange: save,
395
+ });
396
+ }
397
+
398
+ function changeLocale(nextLocale: SimpleMailLocale) {
399
+ const currentDoc = editor?.getValue();
400
+ editor?.destroy();
401
+ mount(nextLocale, currentDoc);
402
+ }
403
+ ```
404
+
405
+ ### 自定义组件国际化
406
+
407
+ 自定义组件的 `name`、`paletteTooltip`、`schema.label`、`schema.placeholder`、`schema.help`、`inlineEditable.placeholder` 由业务侧维护。推荐暴露一个工厂函数,按语言返回组件定义:
408
+
409
+ ```ts
410
+ import { defineBlock, type BlockDefinition, type SimpleMailLocale } from '@simple-mail/core';
411
+ import { createBuiltinBlocks } from '@simple-mail/blocks';
412
+
413
+ function couponBlock(locale: SimpleMailLocale): BlockDefinition<any> {
414
+ const en = locale === 'en-US';
415
+ return defineBlock({
416
+ type: 'custom:coupon',
417
+ name: en ? 'Coupon' : '优惠券',
418
+ category: 'custom',
419
+ defaultProps: { title: en ? 'Exclusive offer' : '专属优惠' },
420
+ schema: [
421
+ { key: 'title', label: en ? 'Title' : '标题', type: 'text' },
422
+ { key: 'code', label: en ? 'Code' : '券码', type: 'text' },
423
+ ],
424
+ toMjml: (p) => `<mj-text>${p.title}</mj-text>`,
425
+ renderPreview: (p) => `<div>${p.title}</div>`,
426
+ });
427
+ }
428
+
429
+ function createBlocks(locale: SimpleMailLocale) {
430
+ return [
431
+ ...createBuiltinBlocks({ locale }),
432
+ couponBlock(locale),
433
+ ];
434
+ }
435
+ ```
436
+
437
+ 如果需要在保持同一份 block 实现的同时只替换显示文案,也可以在宿主侧对 `BlockDefinition` 做浅拷贝并替换 `name`、`schema` 等字段。
438
+
439
+ ---
440
+
300
441
  ## UI 选项 `ui`
301
442
 
302
443
  `MailEditor` 的 `ui?: EditorUiOptions`:
@@ -327,6 +468,8 @@ editor.getTheme();
327
468
  |------|------|
328
469
  | `container` | 挂载 DOM(必填) |
329
470
  | `blocks?` | 块定义列表 |
471
+ | `locale?` | `zh-CN` \| `en-US`,默认 `zh-CN` |
472
+ | `messages?` | 覆盖内置 i18n 文案 |
330
473
  | `engine?` | 目前仅 `'mjml'`;预留 `'table'` |
331
474
  | `initialDoc?` | 首次进入画布 |
332
475
  | `presetDoc?` | 「重置内容」目标 |
@@ -451,7 +594,7 @@ import {
451
594
  |------|------|
452
595
  | `uploadImage?(file, ctx)` | 右栏「上传」→ HTTPS URL |
453
596
  | `imageGallery?` | 内置图库弹层(`showGallery: true`) |
454
- | `pickImageFromGallery?(ctx)` | 自管图床;与 `imageGallery` 并存时**优先内置图库** |
597
+ | `pickImageFromGallery?(ctx)` | 自管图库;与 `imageGallery` 并存时**优先内置图库** |
455
598
  | `showUpload?` | 默认 `true`(有 `uploadImage` 时) |
456
599
  | `showGallery?` | 默认 `false` |
457
600
 
@@ -473,31 +616,34 @@ import { openImageGalleryModal } from '@simple-mail/core';
473
616
 
474
617
  ```tsx
475
618
  import { useEffect, useRef } from 'react';
476
- import { MailEditor, type EmailDoc } from '@simple-mail/core';
619
+ import { MailEditor, type EmailDoc, type SimpleMailLocale } from '@simple-mail/core';
477
620
  import '@simple-mail/core/style.css';
478
- import { allBlocks } from '@simple-mail/blocks';
621
+ import { createAllBlocks } from '@simple-mail/blocks';
479
622
 
480
623
  export function MailEditorView({
481
624
  value,
482
625
  presetDoc,
483
626
  onChange,
627
+ locale = 'zh-CN',
484
628
  }: {
485
629
  value?: Partial<EmailDoc>;
486
630
  presetDoc?: Partial<EmailDoc>;
631
+ locale?: SimpleMailLocale;
487
632
  onChange?: (d: EmailDoc) => void;
488
633
  }) {
489
634
  const ref = useRef<HTMLDivElement>(null);
490
635
  useEffect(() => {
491
636
  const editor = new MailEditor({
492
637
  container: ref.current!,
493
- blocks: allBlocks,
638
+ locale,
639
+ blocks: createAllBlocks({ locale }),
494
640
  initialDoc: value,
495
641
  presetDoc,
496
642
  ui: { hideTopbarTitle: true },
497
643
  onChange,
498
644
  });
499
645
  return () => editor.destroy();
500
- }, []);
646
+ }, [locale]);
501
647
  return <div ref={ref} style={{ height: '100vh' }} />;
502
648
  }
503
649
  ```
@@ -507,13 +653,19 @@ export function MailEditorView({
507
653
  ```vue
508
654
  <script setup lang="ts">
509
655
  import { markRaw, onMounted, onBeforeUnmount, ref } from 'vue';
510
- import { MailEditor, type BlockDefinition, type EmailDoc } from '@simple-mail/core';
656
+ import {
657
+ MailEditor,
658
+ type BlockDefinition,
659
+ type EmailDoc,
660
+ type SimpleMailLocale,
661
+ } from '@simple-mail/core';
511
662
  import '@simple-mail/core/style.css';
512
- import { allBlocks } from '@simple-mail/blocks';
663
+ import { createAllBlocks } from '@simple-mail/blocks';
513
664
 
514
665
  const props = defineProps<{
515
666
  modelValue?: Partial<EmailDoc>;
516
667
  presetDoc?: Partial<EmailDoc>;
668
+ locale?: SimpleMailLocale;
517
669
  blocks?: BlockDefinition<any>[];
518
670
  }>();
519
671
  const emit = defineEmits<{ 'update:modelValue': [EmailDoc] }>();
@@ -521,10 +673,12 @@ const el = ref<HTMLDivElement>();
521
673
  let editor: MailEditor | null = null;
522
674
 
523
675
  onMounted(() => {
524
- const defs = props.blocks ?? allBlocks;
676
+ const locale = props.locale ?? 'zh-CN';
677
+ const defs = props.blocks ?? createAllBlocks({ locale });
525
678
  const stable = defs.map((d) => markRaw(d));
526
679
  editor = new MailEditor({
527
680
  container: el.value!,
681
+ locale,
528
682
  blocks: stable,
529
683
  initialDoc: props.modelValue,
530
684
  presetDoc: props.presetDoc,
@@ -548,7 +702,8 @@ onBeforeUnmount(() => editor?.destroy());
548
702
  ## 自定义组件
549
703
 
550
704
  ```ts
551
- import { defineBlock } from '@simple-mail/core';
705
+ import { defineBlock, MailEditor } from '@simple-mail/core';
706
+ import { createAllBlocks } from '@simple-mail/blocks';
552
707
 
553
708
  export const couponBlock = defineBlock<{ title: string; code: string }>({
554
709
  type: 'custom:coupon',
@@ -571,7 +726,12 @@ export const couponBlock = defineBlock<{ title: string; code: string }>({
571
726
  renderPreview: (p) => `<div class="coupon-title">…</div>`,
572
727
  });
573
728
 
574
- new MailEditor({ container, blocks: [...allBlocks, couponBlock] });
729
+ const locale = 'zh-CN';
730
+ new MailEditor({
731
+ container,
732
+ locale,
733
+ blocks: [...createAllBlocks({ locale }), couponBlock],
734
+ });
575
735
  ```
576
736
 
577
737
  ### `schema` 字段类型
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mail-editor-pancake",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "面向运营的轻量邮件可视化编辑器",
6
6
  "devDependencies": {