universal-email-templates-cli 1.0.0
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/LICENSE +21 -0
- package/README.md +135 -0
- package/bin/build-i18n.js +122 -0
- package/bin/build.js +65 -0
- package/bin/export.js +116 -0
- package/config/translations.example.js +110 -0
- package/index.js +23 -0
- package/lib/builder.js +109 -0
- package/lib/exporter.js +201 -0
- package/lib/translator.js +220 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Universal Email Templates CLI
|
|
2
|
+
|
|
3
|
+
CLI tools for building, translating, and exporting professional MJML email templates.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Build MJML templates to HTML
|
|
8
|
+
- Multi-language support (FR, EN, ES, DE, IT, PT, NL, AR)
|
|
9
|
+
- Export to Mailchimp, Klaviyo, and HubSpot formats
|
|
10
|
+
- WCAG 2.1 AAA accessibility compliant
|
|
11
|
+
- Dark mode support
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g universal-email-templates-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or as a project dependency:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install universal-email-templates-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## CLI Usage
|
|
26
|
+
|
|
27
|
+
### Build Templates
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Build all MJML templates to HTML
|
|
31
|
+
email-build --src ./src/templates --dist ./dist/html
|
|
32
|
+
|
|
33
|
+
# Build with options
|
|
34
|
+
email-build --src ./src/templates --dist ./dist/html --minify
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Internationalization
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Build all languages
|
|
41
|
+
email-i18n --all --src ./src/templates --dist ./dist/i18n
|
|
42
|
+
|
|
43
|
+
# Build specific language
|
|
44
|
+
email-i18n --lang fr --src ./src/templates --dist ./dist/i18n
|
|
45
|
+
|
|
46
|
+
# Build with themes
|
|
47
|
+
email-i18n --lang en --themed --src ./src/templates --dist ./dist/i18n
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Export to Platforms
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Export to Mailchimp
|
|
54
|
+
email-export --platform mailchimp --src ./dist/html --dist ./dist/mailchimp
|
|
55
|
+
|
|
56
|
+
# Export to Klaviyo
|
|
57
|
+
email-export --platform klaviyo --src ./dist/html --dist ./dist/klaviyo
|
|
58
|
+
|
|
59
|
+
# Export to HubSpot
|
|
60
|
+
email-export --platform hubspot --src ./dist/html --dist ./dist/hubspot
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Programmatic Usage
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const { build, translate, exportTo } = require('universal-email-templates-cli');
|
|
67
|
+
|
|
68
|
+
// Build templates
|
|
69
|
+
await build({
|
|
70
|
+
src: './src/templates',
|
|
71
|
+
dist: './dist/html',
|
|
72
|
+
minify: true
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Translate templates
|
|
76
|
+
await translate({
|
|
77
|
+
src: './src/templates',
|
|
78
|
+
dist: './dist/i18n',
|
|
79
|
+
languages: ['fr', 'en', 'es'],
|
|
80
|
+
translations: require('./config/translations')
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Export to platform
|
|
84
|
+
await exportTo({
|
|
85
|
+
platform: 'mailchimp',
|
|
86
|
+
src: './dist/html',
|
|
87
|
+
dist: './dist/mailchimp'
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
### Translations
|
|
94
|
+
|
|
95
|
+
Create a `translations.js` file:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
module.exports = {
|
|
99
|
+
en: {
|
|
100
|
+
greeting_hello: 'Hello',
|
|
101
|
+
btn_get_started: 'Get Started',
|
|
102
|
+
unsubscribe: 'Unsubscribe'
|
|
103
|
+
},
|
|
104
|
+
fr: {
|
|
105
|
+
greeting_hello: 'Bonjour',
|
|
106
|
+
btn_get_started: 'Commencer',
|
|
107
|
+
unsubscribe: 'Se desabonner'
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Platform Variable Mappings
|
|
113
|
+
|
|
114
|
+
| Our Variable | Mailchimp | Klaviyo | HubSpot |
|
|
115
|
+
|--------------|-----------|---------|---------|
|
|
116
|
+
| `{{first_name}}` | `*\|FNAME\|*` | `{{ first_name }}` | `{{ contact.firstname }}` |
|
|
117
|
+
| `{{email}}` | `*\|EMAIL\|*` | `{{ email }}` | `{{ contact.email }}` |
|
|
118
|
+
| `{{unsubscribe_url}}` | `*\|UNSUB\|*` | `{% unsubscribe_url %}` | `{{ unsubscribe_link }}` |
|
|
119
|
+
|
|
120
|
+
## Supported Languages
|
|
121
|
+
|
|
122
|
+
| Code | Language |
|
|
123
|
+
|------|----------|
|
|
124
|
+
| en | English |
|
|
125
|
+
| fr | French |
|
|
126
|
+
| es | Spanish |
|
|
127
|
+
| de | German |
|
|
128
|
+
| it | Italian |
|
|
129
|
+
| pt | Portuguese |
|
|
130
|
+
| nl | Dutch |
|
|
131
|
+
| ar | Arabic |
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI: Build templates with internationalization
|
|
5
|
+
* Usage: email-i18n --lang fr --src ./src --dist ./dist
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { translate, DEFAULT_LANGUAGES } = require('../lib/translator');
|
|
11
|
+
|
|
12
|
+
// Parse command line arguments
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
|
|
15
|
+
function getArg(name, defaultValue) {
|
|
16
|
+
const index = args.indexOf(`--${name}`);
|
|
17
|
+
if (index !== -1 && args[index + 1]) {
|
|
18
|
+
return args[index + 1];
|
|
19
|
+
}
|
|
20
|
+
return defaultValue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasFlag(name) {
|
|
24
|
+
return args.includes(`--${name}`) || args.includes(`-${name[0]}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Show help
|
|
28
|
+
if (hasFlag('help') || hasFlag('h')) {
|
|
29
|
+
console.log(`
|
|
30
|
+
🌍 Universal Email Templates - i18n Build CLI
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
email-i18n [options]
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
--all Build all supported languages
|
|
37
|
+
--lang <code> Build specific language (en, fr, es, de, it, pt, nl, ar)
|
|
38
|
+
--src <path> Source directory with MJML files (default: ./src/templates)
|
|
39
|
+
--dist <path> Destination directory (default: ./dist/i18n)
|
|
40
|
+
--config <path> Path to translations.js config file
|
|
41
|
+
--silent Suppress console output
|
|
42
|
+
--help, -h Show this help message
|
|
43
|
+
|
|
44
|
+
Supported Languages:
|
|
45
|
+
${DEFAULT_LANGUAGES.join(', ')}
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
email-i18n --all
|
|
49
|
+
email-i18n --lang fr
|
|
50
|
+
email-i18n --lang en --src ./templates --dist ./output
|
|
51
|
+
email-i18n --all --config ./my-translations.js
|
|
52
|
+
`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Load translations config
|
|
57
|
+
let translations = {};
|
|
58
|
+
const configPath = getArg('config', null);
|
|
59
|
+
|
|
60
|
+
if (configPath) {
|
|
61
|
+
const fullConfigPath = path.resolve(configPath);
|
|
62
|
+
if (fs.existsSync(fullConfigPath)) {
|
|
63
|
+
translations = require(fullConfigPath);
|
|
64
|
+
console.log(`📖 Loaded translations from: ${fullConfigPath}\n`);
|
|
65
|
+
} else {
|
|
66
|
+
console.error(`❌ Config file not found: ${fullConfigPath}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// Try to find translations.js in common locations
|
|
71
|
+
const possiblePaths = [
|
|
72
|
+
'./config/translations.js',
|
|
73
|
+
'./translations.js',
|
|
74
|
+
'../config/translations.js'
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const p of possiblePaths) {
|
|
78
|
+
const fullPath = path.resolve(p);
|
|
79
|
+
if (fs.existsSync(fullPath)) {
|
|
80
|
+
translations = require(fullPath);
|
|
81
|
+
console.log(`📖 Auto-loaded translations from: ${fullPath}\n`);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Determine languages to build
|
|
88
|
+
let languages = DEFAULT_LANGUAGES;
|
|
89
|
+
const specificLang = getArg('lang', null);
|
|
90
|
+
|
|
91
|
+
if (specificLang) {
|
|
92
|
+
if (!DEFAULT_LANGUAGES.includes(specificLang)) {
|
|
93
|
+
console.error(`❌ Unsupported language: ${specificLang}`);
|
|
94
|
+
console.log(`Supported: ${DEFAULT_LANGUAGES.join(', ')}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
languages = [specificLang];
|
|
98
|
+
} else if (!hasFlag('all')) {
|
|
99
|
+
// Default to just English if no flag specified
|
|
100
|
+
languages = ['en'];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Run translation build
|
|
104
|
+
const options = {
|
|
105
|
+
src: getArg('src', './src/templates'),
|
|
106
|
+
dist: getArg('dist', './dist/i18n'),
|
|
107
|
+
languages,
|
|
108
|
+
translations,
|
|
109
|
+
silent: hasFlag('silent')
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
translate(options)
|
|
113
|
+
.then(results => {
|
|
114
|
+
const hasErrors = Object.values(results).some(r => r.errors > 0);
|
|
115
|
+
if (hasErrors) {
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.catch(error => {
|
|
120
|
+
console.error('❌ i18n build failed:', error.message);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
package/bin/build.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI: Build MJML templates to HTML
|
|
5
|
+
* Usage: email-build --src ./src --dist ./dist
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { build } = require('../lib/builder');
|
|
9
|
+
|
|
10
|
+
// Parse command line arguments
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
|
|
13
|
+
function getArg(name, defaultValue) {
|
|
14
|
+
const index = args.indexOf(`--${name}`);
|
|
15
|
+
if (index !== -1 && args[index + 1]) {
|
|
16
|
+
return args[index + 1];
|
|
17
|
+
}
|
|
18
|
+
return defaultValue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hasFlag(name) {
|
|
22
|
+
return args.includes(`--${name}`) || args.includes(`-${name[0]}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Show help
|
|
26
|
+
if (hasFlag('help') || hasFlag('h')) {
|
|
27
|
+
console.log(`
|
|
28
|
+
📧 Universal Email Templates - Build CLI
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
email-build [options]
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--src <path> Source directory with MJML files (default: ./src/templates)
|
|
35
|
+
--dist <path> Destination directory for HTML (default: ./dist/html)
|
|
36
|
+
--minify Minify output HTML
|
|
37
|
+
--silent Suppress console output
|
|
38
|
+
--help, -h Show this help message
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
email-build
|
|
42
|
+
email-build --src ./templates --dist ./output
|
|
43
|
+
email-build --minify
|
|
44
|
+
`);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Run build
|
|
49
|
+
const options = {
|
|
50
|
+
src: getArg('src', './src/templates'),
|
|
51
|
+
dist: getArg('dist', './dist/html'),
|
|
52
|
+
minify: hasFlag('minify'),
|
|
53
|
+
silent: hasFlag('silent')
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
build(options)
|
|
57
|
+
.then(result => {
|
|
58
|
+
if (result.errors > 0) {
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.catch(error => {
|
|
63
|
+
console.error('❌ Build failed:', error.message);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
});
|
package/bin/export.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI: Export templates to email platforms
|
|
5
|
+
* Usage: email-export --platform mailchimp --src ./dist/html --dist ./dist/mailchimp
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { exportTo, exportAll, platforms } = require('../lib/exporter');
|
|
9
|
+
|
|
10
|
+
// Parse command line arguments
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
|
|
13
|
+
function getArg(name, defaultValue) {
|
|
14
|
+
const index = args.indexOf(`--${name}`);
|
|
15
|
+
if (index !== -1 && args[index + 1]) {
|
|
16
|
+
return args[index + 1];
|
|
17
|
+
}
|
|
18
|
+
return defaultValue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hasFlag(name) {
|
|
22
|
+
return args.includes(`--${name}`) || args.includes(`-${name[0]}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const supportedPlatforms = Object.keys(platforms);
|
|
26
|
+
|
|
27
|
+
// Show help
|
|
28
|
+
if (hasFlag('help') || hasFlag('h')) {
|
|
29
|
+
console.log(`
|
|
30
|
+
📤 Universal Email Templates - Export CLI
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
email-export --platform <name> [options]
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
--platform <name> Target platform: ${supportedPlatforms.join(', ')}
|
|
37
|
+
--all Export to all platforms
|
|
38
|
+
--src <path> Source directory with HTML files (default: ./dist/html)
|
|
39
|
+
--dist <path> Destination directory (default: ./dist/<platform>)
|
|
40
|
+
--silent Suppress console output
|
|
41
|
+
--help, -h Show this help message
|
|
42
|
+
|
|
43
|
+
Supported Platforms:
|
|
44
|
+
mailchimp - Mailchimp merge tags (*|FNAME|*, etc.)
|
|
45
|
+
klaviyo - Klaviyo template tags ({{ first_name }}, etc.)
|
|
46
|
+
hubspot - HubSpot personalization tokens ({{ contact.firstname }}, etc.)
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
email-export --platform mailchimp
|
|
50
|
+
email-export --platform klaviyo --src ./html --dist ./klaviyo
|
|
51
|
+
email-export --all
|
|
52
|
+
`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Run export
|
|
57
|
+
async function run() {
|
|
58
|
+
const src = getArg('src', './dist/html');
|
|
59
|
+
const silent = hasFlag('silent');
|
|
60
|
+
|
|
61
|
+
if (hasFlag('all')) {
|
|
62
|
+
// Export to all platforms
|
|
63
|
+
console.log('🚀 Exporting to all platforms...\n');
|
|
64
|
+
|
|
65
|
+
const results = await exportAll({ src, silent });
|
|
66
|
+
|
|
67
|
+
console.log('\n' + '='.repeat(50));
|
|
68
|
+
console.log('📊 Export Summary:');
|
|
69
|
+
console.log('='.repeat(50));
|
|
70
|
+
|
|
71
|
+
let totalSuccess = 0;
|
|
72
|
+
let totalErrors = 0;
|
|
73
|
+
|
|
74
|
+
for (const [platform, result] of Object.entries(results)) {
|
|
75
|
+
console.log(` ${platforms[platform].name}: ${result.success} success, ${result.errors} errors`);
|
|
76
|
+
totalSuccess += result.success;
|
|
77
|
+
totalErrors += result.errors;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('='.repeat(50));
|
|
81
|
+
console.log(` TOTAL: ${totalSuccess} success, ${totalErrors} errors\n`);
|
|
82
|
+
|
|
83
|
+
if (totalErrors > 0) {
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
// Export to specific platform
|
|
88
|
+
const platform = getArg('platform', null);
|
|
89
|
+
|
|
90
|
+
if (!platform) {
|
|
91
|
+
console.error('❌ Please specify a platform with --platform <name>');
|
|
92
|
+
console.log(`Supported: ${supportedPlatforms.join(', ')}`);
|
|
93
|
+
console.log('Or use --all to export to all platforms');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!supportedPlatforms.includes(platform)) {
|
|
98
|
+
console.error(`❌ Unsupported platform: ${platform}`);
|
|
99
|
+
console.log(`Supported: ${supportedPlatforms.join(', ')}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const dist = getArg('dist', `./dist/${platform}`);
|
|
104
|
+
|
|
105
|
+
const result = await exportTo({ platform, src, dist, silent });
|
|
106
|
+
|
|
107
|
+
if (result.errors > 0) {
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
run().catch(error => {
|
|
114
|
+
console.error('❌ Export failed:', error.message);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example translations configuration
|
|
3
|
+
* Copy this file to translations.js and customize
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
en: {
|
|
8
|
+
// Greetings
|
|
9
|
+
greeting_hello: 'Hello',
|
|
10
|
+
greeting_hi: 'Hi',
|
|
11
|
+
greeting_dear: 'Dear',
|
|
12
|
+
greeting_welcome: 'Welcome',
|
|
13
|
+
welcome_aboard: 'Welcome aboard!',
|
|
14
|
+
|
|
15
|
+
// Buttons
|
|
16
|
+
btn_get_started: 'Get Started',
|
|
17
|
+
btn_learn_more: 'Learn More',
|
|
18
|
+
btn_shop_now: 'Shop Now',
|
|
19
|
+
btn_view_details: 'View Details',
|
|
20
|
+
btn_subscribe: 'Subscribe',
|
|
21
|
+
btn_confirm: 'Confirm',
|
|
22
|
+
btn_reset_password: 'Reset Password',
|
|
23
|
+
btn_verify_email: 'Verify Email',
|
|
24
|
+
btn_view_order: 'View Order',
|
|
25
|
+
btn_track_order: 'Track Order',
|
|
26
|
+
|
|
27
|
+
// Common UI
|
|
28
|
+
view_in_browser: 'View in browser',
|
|
29
|
+
unsubscribe: 'Unsubscribe',
|
|
30
|
+
privacy_policy: 'Privacy Policy',
|
|
31
|
+
terms_of_service: 'Terms of Service',
|
|
32
|
+
contact_us: 'Contact Us',
|
|
33
|
+
all_rights_reserved: 'All rights reserved',
|
|
34
|
+
|
|
35
|
+
// Order related
|
|
36
|
+
order_number: 'Order Number',
|
|
37
|
+
order_date: 'Order Date',
|
|
38
|
+
order_total: 'Total',
|
|
39
|
+
order_subtotal: 'Subtotal',
|
|
40
|
+
order_shipping: 'Shipping',
|
|
41
|
+
order_tax: 'Tax',
|
|
42
|
+
shipping_address: 'Shipping Address',
|
|
43
|
+
billing_address: 'Billing Address',
|
|
44
|
+
|
|
45
|
+
// Common phrases
|
|
46
|
+
thank_you: 'Thank you',
|
|
47
|
+
questions_reply: 'Questions? Just reply to this email.'
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
fr: {
|
|
51
|
+
// Greetings
|
|
52
|
+
greeting_hello: 'Bonjour',
|
|
53
|
+
greeting_hi: 'Salut',
|
|
54
|
+
greeting_dear: 'Cher',
|
|
55
|
+
greeting_welcome: 'Bienvenue',
|
|
56
|
+
welcome_aboard: 'Bienvenue parmi nous !',
|
|
57
|
+
|
|
58
|
+
// Buttons
|
|
59
|
+
btn_get_started: 'Commencer',
|
|
60
|
+
btn_learn_more: 'En savoir plus',
|
|
61
|
+
btn_shop_now: 'Acheter maintenant',
|
|
62
|
+
btn_view_details: 'Voir les details',
|
|
63
|
+
btn_subscribe: "S'abonner",
|
|
64
|
+
btn_confirm: 'Confirmer',
|
|
65
|
+
btn_reset_password: 'Reinitialiser le mot de passe',
|
|
66
|
+
btn_verify_email: "Verifier l'email",
|
|
67
|
+
btn_view_order: 'Voir la commande',
|
|
68
|
+
btn_track_order: 'Suivre la commande',
|
|
69
|
+
|
|
70
|
+
// Common UI
|
|
71
|
+
view_in_browser: 'Voir dans le navigateur',
|
|
72
|
+
unsubscribe: 'Se desabonner',
|
|
73
|
+
privacy_policy: 'Politique de confidentialite',
|
|
74
|
+
terms_of_service: "Conditions d'utilisation",
|
|
75
|
+
contact_us: 'Nous contacter',
|
|
76
|
+
all_rights_reserved: 'Tous droits reserves',
|
|
77
|
+
|
|
78
|
+
// Order related
|
|
79
|
+
order_number: 'Numero de commande',
|
|
80
|
+
order_date: 'Date de commande',
|
|
81
|
+
order_total: 'Total',
|
|
82
|
+
order_subtotal: 'Sous-total',
|
|
83
|
+
order_shipping: 'Livraison',
|
|
84
|
+
order_tax: 'TVA',
|
|
85
|
+
shipping_address: 'Adresse de livraison',
|
|
86
|
+
billing_address: 'Adresse de facturation',
|
|
87
|
+
|
|
88
|
+
// Common phrases
|
|
89
|
+
thank_you: 'Merci',
|
|
90
|
+
questions_reply: 'Des questions ? Repondez simplement a cet email.'
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
es: {
|
|
94
|
+
greeting_hello: 'Hola',
|
|
95
|
+
greeting_welcome: 'Bienvenido',
|
|
96
|
+
btn_get_started: 'Comenzar',
|
|
97
|
+
btn_learn_more: 'Saber mas',
|
|
98
|
+
unsubscribe: 'Darse de baja',
|
|
99
|
+
thank_you: 'Gracias'
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
de: {
|
|
103
|
+
greeting_hello: 'Hallo',
|
|
104
|
+
greeting_welcome: 'Willkommen',
|
|
105
|
+
btn_get_started: 'Loslegen',
|
|
106
|
+
btn_learn_more: 'Mehr erfahren',
|
|
107
|
+
unsubscribe: 'Abmelden',
|
|
108
|
+
thank_you: 'Danke'
|
|
109
|
+
}
|
|
110
|
+
};
|
package/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Email Templates CLI
|
|
3
|
+
* Build, translate, and export MJML email templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { build } = require('./lib/builder');
|
|
7
|
+
const { translate, applyTranslations } = require('./lib/translator');
|
|
8
|
+
const { exportTo, platforms } = require('./lib/exporter');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
// Main functions
|
|
12
|
+
build,
|
|
13
|
+
translate,
|
|
14
|
+
exportTo,
|
|
15
|
+
|
|
16
|
+
// Utilities
|
|
17
|
+
applyTranslations,
|
|
18
|
+
platforms,
|
|
19
|
+
|
|
20
|
+
// Constants
|
|
21
|
+
supportedLanguages: ['en', 'fr', 'es', 'de', 'it', 'pt', 'nl', 'ar'],
|
|
22
|
+
supportedPlatforms: ['mailchimp', 'klaviyo', 'hubspot']
|
|
23
|
+
};
|
package/lib/builder.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MJML Build Functions
|
|
3
|
+
* Compiles MJML templates to HTML
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const glob = require('glob');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build MJML templates to HTML
|
|
13
|
+
* @param {Object} options - Build options
|
|
14
|
+
* @param {string} options.src - Source directory with MJML files
|
|
15
|
+
* @param {string} options.dist - Destination directory for HTML files
|
|
16
|
+
* @param {boolean} [options.minify=false] - Minify output HTML
|
|
17
|
+
* @param {boolean} [options.silent=false] - Suppress console output
|
|
18
|
+
* @returns {Promise<{success: number, errors: number, files: string[]}>}
|
|
19
|
+
*/
|
|
20
|
+
async function build(options = {}) {
|
|
21
|
+
const {
|
|
22
|
+
src = './src/templates',
|
|
23
|
+
dist = './dist/html',
|
|
24
|
+
minify = false,
|
|
25
|
+
silent = false
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
const srcDir = path.resolve(src);
|
|
29
|
+
const distDir = path.resolve(dist);
|
|
30
|
+
|
|
31
|
+
// Ensure dist directory exists
|
|
32
|
+
await fs.ensureDir(distDir);
|
|
33
|
+
|
|
34
|
+
// Find all MJML files
|
|
35
|
+
const mjmlFiles = glob.sync('**/*.mjml', { cwd: srcDir });
|
|
36
|
+
|
|
37
|
+
if (!silent) {
|
|
38
|
+
console.log(`\n🚀 Building ${mjmlFiles.length} MJML templates...\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let successCount = 0;
|
|
42
|
+
let errorCount = 0;
|
|
43
|
+
const processedFiles = [];
|
|
44
|
+
|
|
45
|
+
for (const file of mjmlFiles) {
|
|
46
|
+
const inputPath = path.join(srcDir, file);
|
|
47
|
+
const outputPath = path.join(distDir, file.replace('.mjml', '.html'));
|
|
48
|
+
const outputDir = path.dirname(outputPath);
|
|
49
|
+
|
|
50
|
+
// Ensure output subdirectory exists
|
|
51
|
+
await fs.ensureDir(outputDir);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const minifyFlag = minify ? '--config.minify=true' : '';
|
|
55
|
+
execSync(`npx mjml "${inputPath}" -o "${outputPath}" ${minifyFlag}`, {
|
|
56
|
+
stdio: 'pipe'
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!silent) {
|
|
60
|
+
console.log(`✅ ${file}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
successCount++;
|
|
64
|
+
processedFiles.push(outputPath);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (!silent) {
|
|
67
|
+
console.log(`❌ ${file}`);
|
|
68
|
+
console.log(` Error: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
errorCount++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!silent) {
|
|
75
|
+
console.log(`\n📊 Build complete: ${successCount} success, ${errorCount} errors`);
|
|
76
|
+
console.log(`📁 Output: ${distDir}\n`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
success: successCount,
|
|
81
|
+
errors: errorCount,
|
|
82
|
+
files: processedFiles
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build a single MJML file
|
|
88
|
+
* @param {string} inputPath - Path to MJML file
|
|
89
|
+
* @param {string} outputPath - Path for output HTML
|
|
90
|
+
* @param {Object} [options] - Build options
|
|
91
|
+
* @returns {Promise<string>} - Output HTML content
|
|
92
|
+
*/
|
|
93
|
+
async function buildSingle(inputPath, outputPath, options = {}) {
|
|
94
|
+
const { minify = false } = options;
|
|
95
|
+
|
|
96
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
97
|
+
|
|
98
|
+
const minifyFlag = minify ? '--config.minify=true' : '';
|
|
99
|
+
execSync(`npx mjml "${inputPath}" -o "${outputPath}" ${minifyFlag}`, {
|
|
100
|
+
stdio: 'pipe'
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return fs.readFile(outputPath, 'utf8');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
build,
|
|
108
|
+
buildSingle
|
|
109
|
+
};
|
package/lib/exporter.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Export Functions
|
|
3
|
+
* Convert templates to Mailchimp, Klaviyo, HubSpot formats
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const glob = require('glob');
|
|
9
|
+
const cheerio = require('cheerio');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Platform-specific variable mappings
|
|
13
|
+
*/
|
|
14
|
+
const platforms = {
|
|
15
|
+
mailchimp: {
|
|
16
|
+
name: 'Mailchimp',
|
|
17
|
+
variables: {
|
|
18
|
+
'{{first_name}}': '*|FNAME|*',
|
|
19
|
+
'{{last_name}}': '*|LNAME|*',
|
|
20
|
+
'{{email}}': '*|EMAIL|*',
|
|
21
|
+
'{{company_name}}': '*|LIST:COMPANY|*',
|
|
22
|
+
'{{company_address}}': '*|LIST:ADDRESS|*',
|
|
23
|
+
'{{unsubscribe_url}}': '*|UNSUB|*',
|
|
24
|
+
'{{preferences_url}}': '*|UPDATE_PROFILE|*',
|
|
25
|
+
'{{view_in_browser}}': '*|ARCHIVE|*',
|
|
26
|
+
'{{current_year}}': '*|CURRENT_YEAR|*',
|
|
27
|
+
'{{forward_url}}': '*|FORWARD|*'
|
|
28
|
+
},
|
|
29
|
+
trackingPixel: '<img src="*|OPEN_TRACKING_URL|*" width="1" height="1" style="display:none;">'
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
klaviyo: {
|
|
33
|
+
name: 'Klaviyo',
|
|
34
|
+
variables: {
|
|
35
|
+
'{{first_name}}': '{{ first_name|default:"" }}',
|
|
36
|
+
'{{last_name}}': '{{ last_name|default:"" }}',
|
|
37
|
+
'{{email}}': '{{ email }}',
|
|
38
|
+
'{{company_name}}': '{{ organization.name }}',
|
|
39
|
+
'{{company_address}}': '{{ organization.address }}',
|
|
40
|
+
'{{unsubscribe_url}}': '{% unsubscribe_url %}',
|
|
41
|
+
'{{preferences_url}}': '{% manage_preferences_url %}',
|
|
42
|
+
'{{view_in_browser}}': '{% view_in_browser_url %}',
|
|
43
|
+
'{{current_year}}': '{{ "now"|date:"Y" }}'
|
|
44
|
+
},
|
|
45
|
+
trackingPixel: null
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
hubspot: {
|
|
49
|
+
name: 'HubSpot',
|
|
50
|
+
variables: {
|
|
51
|
+
'{{first_name}}': '{{ contact.firstname }}',
|
|
52
|
+
'{{last_name}}': '{{ contact.lastname }}',
|
|
53
|
+
'{{email}}': '{{ contact.email }}',
|
|
54
|
+
'{{company_name}}': '{{ site_settings.company_name }}',
|
|
55
|
+
'{{company_address}}': '{{ site_settings.company_street_address_1 }}',
|
|
56
|
+
'{{unsubscribe_url}}': '{{ unsubscribe_link }}',
|
|
57
|
+
'{{preferences_url}}': '{{ unsubscribe_link }}',
|
|
58
|
+
'{{view_in_browser}}': '{{ view_as_page_url }}',
|
|
59
|
+
'{{current_year}}': '{{ year }}'
|
|
60
|
+
},
|
|
61
|
+
trackingPixel: null
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Escape regex special characters
|
|
67
|
+
*/
|
|
68
|
+
function escapeRegex(string) {
|
|
69
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Export templates to a specific platform format
|
|
74
|
+
* @param {Object} options - Export options
|
|
75
|
+
* @param {string} options.platform - Target platform (mailchimp, klaviyo, hubspot)
|
|
76
|
+
* @param {string} options.src - Source directory with HTML files
|
|
77
|
+
* @param {string} options.dist - Destination directory
|
|
78
|
+
* @param {boolean} [options.silent=false] - Suppress console output
|
|
79
|
+
* @returns {Promise<{success: number, errors: number, files: string[]}>}
|
|
80
|
+
*/
|
|
81
|
+
async function exportTo(options = {}) {
|
|
82
|
+
const {
|
|
83
|
+
platform,
|
|
84
|
+
src = './dist/html',
|
|
85
|
+
dist,
|
|
86
|
+
silent = false
|
|
87
|
+
} = options;
|
|
88
|
+
|
|
89
|
+
if (!platform || !platforms[platform]) {
|
|
90
|
+
throw new Error(`Invalid platform. Supported: ${Object.keys(platforms).join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const platformConfig = platforms[platform];
|
|
94
|
+
const srcDir = path.resolve(src);
|
|
95
|
+
const distDir = path.resolve(dist || `./dist/${platform}`);
|
|
96
|
+
|
|
97
|
+
if (!silent) {
|
|
98
|
+
console.log(`🚀 Starting ${platformConfig.name} export...\n`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Ensure destination directory exists
|
|
102
|
+
await fs.ensureDir(distDir);
|
|
103
|
+
|
|
104
|
+
// Get all HTML files
|
|
105
|
+
const htmlFiles = glob.sync('**/*.html', { cwd: srcDir });
|
|
106
|
+
|
|
107
|
+
if (htmlFiles.length === 0) {
|
|
108
|
+
if (!silent) {
|
|
109
|
+
console.log('⚠️ No HTML files found. Run build first.\n');
|
|
110
|
+
}
|
|
111
|
+
return { success: 0, errors: 0, files: [] };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!silent) {
|
|
115
|
+
console.log(`📁 Found ${htmlFiles.length} templates to convert\n`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let successCount = 0;
|
|
119
|
+
let errorCount = 0;
|
|
120
|
+
const processedFiles = [];
|
|
121
|
+
|
|
122
|
+
for (const file of htmlFiles) {
|
|
123
|
+
const sourcePath = path.join(srcDir, file);
|
|
124
|
+
const destPath = path.join(distDir, file);
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Read HTML content
|
|
128
|
+
let html = await fs.readFile(sourcePath, 'utf8');
|
|
129
|
+
|
|
130
|
+
// Replace variables with platform-specific tags
|
|
131
|
+
for (const [ourVar, platformVar] of Object.entries(platformConfig.variables)) {
|
|
132
|
+
html = html.replace(new RegExp(escapeRegex(ourVar), 'g'), platformVar);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add tracking pixel if needed (Mailchimp)
|
|
136
|
+
if (platformConfig.trackingPixel) {
|
|
137
|
+
const $ = cheerio.load(html);
|
|
138
|
+
if (!$('img[src*="TRACKING"]').length && !$('img[src*="tracking"]').length) {
|
|
139
|
+
$('body').append(platformConfig.trackingPixel);
|
|
140
|
+
}
|
|
141
|
+
html = $.html();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Ensure destination subdirectory exists
|
|
145
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
146
|
+
|
|
147
|
+
// Write converted file
|
|
148
|
+
await fs.writeFile(destPath, html);
|
|
149
|
+
|
|
150
|
+
if (!silent) {
|
|
151
|
+
console.log(`✅ ${file}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
successCount++;
|
|
155
|
+
processedFiles.push(destPath);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (!silent) {
|
|
158
|
+
console.log(`❌ ${file}`);
|
|
159
|
+
console.log(` Error: ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
errorCount++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!silent) {
|
|
166
|
+
console.log(`\n🎉 ${platformConfig.name} export complete!`);
|
|
167
|
+
console.log(`📁 Files saved to: ${distDir}\n`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
success: successCount,
|
|
172
|
+
errors: errorCount,
|
|
173
|
+
files: processedFiles
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Export to all platforms at once
|
|
179
|
+
*/
|
|
180
|
+
async function exportAll(options = {}) {
|
|
181
|
+
const { src, distBase = './dist', silent = false } = options;
|
|
182
|
+
const results = {};
|
|
183
|
+
|
|
184
|
+
for (const platform of Object.keys(platforms)) {
|
|
185
|
+
results[platform] = await exportTo({
|
|
186
|
+
platform,
|
|
187
|
+
src,
|
|
188
|
+
dist: path.join(distBase, platform),
|
|
189
|
+
silent
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return results;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
exportTo,
|
|
198
|
+
exportAll,
|
|
199
|
+
platforms,
|
|
200
|
+
escapeRegex
|
|
201
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation Functions
|
|
3
|
+
* Apply translations to MJML/HTML templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const glob = require('glob');
|
|
10
|
+
|
|
11
|
+
// Default supported languages
|
|
12
|
+
const DEFAULT_LANGUAGES = ['en', 'fr', 'es', 'de', 'it', 'pt', 'nl', 'ar'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get translation value
|
|
16
|
+
* @param {Object} translations - Translation object
|
|
17
|
+
* @param {string} key - Translation key
|
|
18
|
+
* @param {string} lang - Language code
|
|
19
|
+
* @returns {string} - Translated text
|
|
20
|
+
*/
|
|
21
|
+
function t(translations, key, lang) {
|
|
22
|
+
if (translations[lang] && translations[lang][key]) {
|
|
23
|
+
return translations[lang][key];
|
|
24
|
+
}
|
|
25
|
+
if (translations.en && translations.en[key]) {
|
|
26
|
+
return translations.en[key];
|
|
27
|
+
}
|
|
28
|
+
return `{{t.${key}}}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Apply translations to content
|
|
33
|
+
* @param {string} content - MJML or HTML content
|
|
34
|
+
* @param {string} lang - Target language
|
|
35
|
+
* @param {Object} translations - Translation dictionary
|
|
36
|
+
* @returns {string} - Translated content
|
|
37
|
+
*/
|
|
38
|
+
function applyTranslations(content, lang, translations) {
|
|
39
|
+
let result = content;
|
|
40
|
+
|
|
41
|
+
// Replace {{t:key}} and {{t.key}} patterns
|
|
42
|
+
const translationPatternColon = /\{\{t:(\w+)\}\}/g;
|
|
43
|
+
const translationPatternDot = /\{\{t\.(\w+)\}\}/g;
|
|
44
|
+
|
|
45
|
+
result = result.replace(translationPatternColon, (match, key) => {
|
|
46
|
+
return t(translations, key, lang);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
result = result.replace(translationPatternDot, (match, key) => {
|
|
50
|
+
return t(translations, key, lang);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Translate templates to multiple languages
|
|
58
|
+
* @param {Object} options - Translation options
|
|
59
|
+
* @param {string} options.src - Source directory with MJML files
|
|
60
|
+
* @param {string} options.dist - Destination directory
|
|
61
|
+
* @param {string[]} [options.languages] - Languages to build
|
|
62
|
+
* @param {Object} options.translations - Translation dictionary
|
|
63
|
+
* @param {Object} [options.themes] - Theme configurations
|
|
64
|
+
* @param {boolean} [options.silent=false] - Suppress console output
|
|
65
|
+
* @returns {Promise<Object>} - Results by language
|
|
66
|
+
*/
|
|
67
|
+
async function translate(options = {}) {
|
|
68
|
+
const {
|
|
69
|
+
src = './src/templates',
|
|
70
|
+
dist = './dist/i18n',
|
|
71
|
+
languages = DEFAULT_LANGUAGES,
|
|
72
|
+
translations = {},
|
|
73
|
+
themes = null,
|
|
74
|
+
silent = false
|
|
75
|
+
} = options;
|
|
76
|
+
|
|
77
|
+
const srcDir = path.resolve(src);
|
|
78
|
+
const distDir = path.resolve(dist);
|
|
79
|
+
const results = {};
|
|
80
|
+
|
|
81
|
+
if (!silent) {
|
|
82
|
+
console.log(`🚀 Building templates in ${languages.length} languages: ${languages.join(', ')}...\n`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const lang of languages) {
|
|
86
|
+
results[lang] = await buildLanguage({
|
|
87
|
+
lang,
|
|
88
|
+
srcDir,
|
|
89
|
+
distDir,
|
|
90
|
+
translations,
|
|
91
|
+
themes,
|
|
92
|
+
silent
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!silent) {
|
|
97
|
+
console.log('\n' + '='.repeat(50));
|
|
98
|
+
console.log('📊 All languages build summary:');
|
|
99
|
+
console.log('='.repeat(50));
|
|
100
|
+
|
|
101
|
+
let totalSuccess = 0;
|
|
102
|
+
let totalErrors = 0;
|
|
103
|
+
|
|
104
|
+
for (const [lang, result] of Object.entries(results)) {
|
|
105
|
+
console.log(` ${lang.toUpperCase()}: ${result.success} success, ${result.errors} errors`);
|
|
106
|
+
totalSuccess += result.success;
|
|
107
|
+
totalErrors += result.errors;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log('='.repeat(50));
|
|
111
|
+
console.log(` TOTAL: ${totalSuccess} success, ${totalErrors} errors\n`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build templates for a specific language
|
|
119
|
+
*/
|
|
120
|
+
async function buildLanguage(options) {
|
|
121
|
+
const { lang, srcDir, distDir, translations, themes, silent } = options;
|
|
122
|
+
|
|
123
|
+
const langDir = path.join(distDir, lang);
|
|
124
|
+
const tempDir = path.join(distDir, '.temp-i18n');
|
|
125
|
+
|
|
126
|
+
// Ensure directories exist
|
|
127
|
+
await fs.ensureDir(langDir);
|
|
128
|
+
await fs.remove(tempDir);
|
|
129
|
+
await fs.ensureDir(tempDir);
|
|
130
|
+
|
|
131
|
+
// Find all MJML files
|
|
132
|
+
const mjmlFiles = glob.sync('**/*.mjml', { cwd: srcDir });
|
|
133
|
+
|
|
134
|
+
if (!silent) {
|
|
135
|
+
console.log(`\n🌍 Building ${mjmlFiles.length} templates in ${lang.toUpperCase()}...\n`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let successCount = 0;
|
|
139
|
+
let errorCount = 0;
|
|
140
|
+
|
|
141
|
+
for (const file of mjmlFiles) {
|
|
142
|
+
const inputPath = path.join(srcDir, file);
|
|
143
|
+
const tempPath = path.join(tempDir, file);
|
|
144
|
+
const outputPath = path.join(langDir, file.replace('.mjml', '.html'));
|
|
145
|
+
|
|
146
|
+
await fs.ensureDir(path.dirname(tempPath));
|
|
147
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Read and translate MJML
|
|
151
|
+
let mjmlContent = await fs.readFile(inputPath, 'utf-8');
|
|
152
|
+
mjmlContent = applyTranslations(mjmlContent, lang, translations);
|
|
153
|
+
|
|
154
|
+
// Apply theme if provided
|
|
155
|
+
if (themes) {
|
|
156
|
+
mjmlContent = applyTheme(mjmlContent, themes);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Write temp file
|
|
160
|
+
await fs.writeFile(tempPath, mjmlContent);
|
|
161
|
+
|
|
162
|
+
// Build MJML to HTML
|
|
163
|
+
execSync(`npx mjml "${tempPath}" -o "${outputPath}"`, { stdio: 'pipe' });
|
|
164
|
+
|
|
165
|
+
if (!silent) {
|
|
166
|
+
console.log(`✅ ${file}`);
|
|
167
|
+
}
|
|
168
|
+
successCount++;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (!silent) {
|
|
171
|
+
console.log(`❌ ${file}`);
|
|
172
|
+
console.log(` Error: ${error.message}`);
|
|
173
|
+
}
|
|
174
|
+
errorCount++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Clean up temp directory
|
|
179
|
+
await fs.remove(tempDir);
|
|
180
|
+
|
|
181
|
+
if (!silent) {
|
|
182
|
+
console.log(`\n📊 Build complete: ${successCount} success, ${errorCount} errors`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { success: successCount, errors: errorCount };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Apply theme colors to content
|
|
190
|
+
*/
|
|
191
|
+
function applyTheme(content, themeColors) {
|
|
192
|
+
let result = content;
|
|
193
|
+
|
|
194
|
+
const colorMappings = {
|
|
195
|
+
'#3B82F6': themeColors.primary,
|
|
196
|
+
'#2563EB': themeColors.primary_dark,
|
|
197
|
+
'#DBEAFE': themeColors.primary_light,
|
|
198
|
+
'#1F2937': themeColors.text_primary,
|
|
199
|
+
'#4B5563': themeColors.text_secondary,
|
|
200
|
+
'#FFFFFF': themeColors.surface,
|
|
201
|
+
'#F9FAFB': themeColors.background
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
for (const [original, replacement] of Object.entries(colorMappings)) {
|
|
205
|
+
if (replacement) {
|
|
206
|
+
const regex = new RegExp(original.replace('#', '\\#'), 'gi');
|
|
207
|
+
result = result.replace(regex, replacement);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
translate,
|
|
216
|
+
applyTranslations,
|
|
217
|
+
buildLanguage,
|
|
218
|
+
applyTheme,
|
|
219
|
+
DEFAULT_LANGUAGES
|
|
220
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "universal-email-templates-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tools for building, translating and exporting professional MJML email templates to Mailchimp, Klaviyo, and HubSpot",
|
|
5
|
+
"author": "Atsé Fabrice Igor KOMAN <your.email@example.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"email-build": "./bin/build.js",
|
|
10
|
+
"email-i18n": "./bin/build-i18n.js",
|
|
11
|
+
"email-export": "./bin/export.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin/",
|
|
15
|
+
"lib/",
|
|
16
|
+
"config/",
|
|
17
|
+
"index.js",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"email-templates",
|
|
22
|
+
"mjml",
|
|
23
|
+
"newsletter",
|
|
24
|
+
"mailchimp",
|
|
25
|
+
"klaviyo",
|
|
26
|
+
"hubspot",
|
|
27
|
+
"email-marketing",
|
|
28
|
+
"responsive-email",
|
|
29
|
+
"i18n",
|
|
30
|
+
"internationalization",
|
|
31
|
+
"cli"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/NumeriKStudio/universal-email-templates-cli.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/NumeriKStudio/universal-email-templates-cli/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/NumeriKStudio/universal-email-templates-cli#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=16.0.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "node bin/build.js",
|
|
46
|
+
"build:i18n": "node bin/build-i18n.js --all",
|
|
47
|
+
"build:lang": "node bin/build-i18n.js",
|
|
48
|
+
"export:mailchimp": "node bin/export.js --platform mailchimp",
|
|
49
|
+
"export:klaviyo": "node bin/export.js --platform klaviyo",
|
|
50
|
+
"export:hubspot": "node bin/export.js --platform hubspot",
|
|
51
|
+
"test": "node test/index.js",
|
|
52
|
+
"prepublishOnly": "npm test"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"cheerio": "^1.0.0-rc.12",
|
|
56
|
+
"fs-extra": "^11.2.0",
|
|
57
|
+
"glob": "^10.3.10",
|
|
58
|
+
"mjml": "^4.15.3"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"jest": "^29.7.0"
|
|
62
|
+
}
|
|
63
|
+
}
|