@mindful-web/marko-web-theme-monorail 1.80.3 → 1.81.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/I18N_GUIDE.md +354 -0
- package/README.md +8 -0
- package/components/ads/fixed-ad-bottom.marko.js +2 -2
- package/components/ads/reveal-ad-handler.marko.js +2 -2
- package/components/blocks/author-bios.marko.js +2 -2
- package/components/blocks/callout-cards.marko.js +2 -2
- package/components/blocks/company-inquiry-form.marko.js +2 -2
- package/components/blocks/company-search.marko.js +2 -2
- package/components/blocks/content-card-deck.marko.js +2 -2
- package/components/blocks/content-meter.marko +27 -9
- package/components/blocks/content-meter.marko.js +29 -10
- package/components/blocks/full-width-native-ad.marko.js +2 -2
- package/components/blocks/geo-summary.marko.js +2 -2
- package/components/blocks/header-announcement.marko.js +2 -2
- package/components/blocks/hero-card.marko.js +2 -2
- package/components/blocks/latest-content-list.marko.js +2 -2
- package/components/blocks/latest-podcast.marko.js +2 -2
- package/components/blocks/latest-products-feed.marko.js +2 -2
- package/components/blocks/more-products.marko.js +2 -2
- package/components/blocks/most-popular-list.marko.js +2 -2
- package/components/blocks/most-popular.marko.js +2 -2
- package/components/blocks/native-x-card-deck.marko.js +2 -2
- package/components/blocks/native-x-promo-card.marko.js +2 -2
- package/components/blocks/newsletter-signup-banner-external.marko.js +2 -2
- package/components/blocks/newsletter-signup-banner-large.marko.js +2 -2
- package/components/blocks/newsletter-signup-banner.marko.js +2 -2
- package/components/blocks/opinion.marko.js +2 -2
- package/components/blocks/primary-image.marko.js +2 -2
- package/components/blocks/products.marko.js +2 -2
- package/components/blocks/read-next.marko.js +2 -2
- package/components/blocks/related-stories.marko.js +2 -2
- package/components/blocks/resource-library.marko.js +2 -2
- package/components/blocks/section-body-feed.marko.js +2 -2
- package/components/blocks/section-card-deck-featured.marko.js +2 -2
- package/components/blocks/section-card-deck.marko.js +2 -2
- package/components/blocks/section-card-list.marko.js +2 -2
- package/components/blocks/section-feed.marko.js +2 -2
- package/components/blocks/section-forbes.marko.js +2 -2
- package/components/blocks/section-list-deck.marko.js +2 -2
- package/components/blocks/section-list.marko.js +2 -2
- package/components/blocks/section-search.marko.js +2 -2
- package/components/blocks/section-title-feed.marko.js +2 -2
- package/components/blocks/top-stories-menu.marko.js +2 -2
- package/components/blocks/top-stories.marko.js +2 -2
- package/components/blocks/white-papers.marko.js +2 -2
- package/components/breadcrumbs/content-page.marko.js +2 -2
- package/components/breadcrumbs/index.marko.js +2 -2
- package/components/breadcrumbs/website-section.marko.js +2 -2
- package/components/breadcrumbs/with-home.marko.js +2 -2
- package/components/client-side-block-loader.marko.js +2 -2
- package/components/client-side-blocks/most-popular-list.marko.js +2 -2
- package/components/client-side-blocks/most-popular.marko.js +2 -2
- package/components/content/body-with-injection.marko.js +2 -2
- package/components/content/contact-details/index.marko.js +2 -2
- package/components/content/contact-details/section.marko.js +2 -2
- package/components/content/download/form-dot-com.marko.js +2 -2
- package/components/content/download/identity-x.marko.js +2 -2
- package/components/content/download/index.marko.js +2 -2
- package/components/content/download/link.marko.js +2 -2
- package/components/content/download/wufoo.marko.js +2 -2
- package/components/content/link-url/index.marko.js +2 -2
- package/components/content/sections-listed-in.marko.js +2 -2
- package/components/content-attribution/attribution.marko.js +2 -2
- package/components/content-attribution/components/authors.marko.js +2 -2
- package/components/content-attribution/components/byline.marko.js +2 -2
- package/components/content-attribution/components/company.marko.js +2 -2
- package/components/content-attribution/components/contributors.marko.js +2 -2
- package/components/content-attribution/components/photographers.marko.js +2 -2
- package/components/content-attribution/components/source.marko.js +2 -2
- package/components/content-attribution/components/speakers.marko.js +2 -2
- package/components/content-attribution/components/sponsors.marko.js +2 -2
- package/components/content-attribution/index.marko.js +2 -2
- package/components/directory-facets/facet-container/facet-container.marko.js +2 -2
- package/components/directory-facets/facet-list/facet-list.marko.js +2 -2
- package/components/directory-facets/facet.marko.js +2 -2
- package/components/directory-facets/index.marko.js +2 -2
- package/components/facebook-connect.marko.js +2 -2
- package/components/flows/card-deck.marko.js +2 -2
- package/components/flows/content/card-deck-col.marko.js +2 -2
- package/components/flows/content/directory-section.marko.js +2 -2
- package/components/flows/content/list.marko.js +2 -2
- package/components/flows/section-feed.marko.js +2 -2
- package/components/gam/components/define-display-ad.marko.js +2 -2
- package/components/gam/define-display-ad.marko.js +2 -2
- package/components/gam/inject-ads.marko.js +2 -2
- package/components/gam/wallpaper-ad.marko.js +2 -2
- package/components/identity-x/comment-stream.marko.js +2 -2
- package/components/identity-x/content-page-gate.marko.js +2 -2
- package/components/identity-x/newsletter-footer.marko.js +2 -2
- package/components/identity-x/newsletter-inline.marko.js +2 -2
- package/components/identity-x/newsletter-modal.marko.js +2 -2
- package/components/identity-x/newsletter-pushdown.marko.js +2 -2
- package/components/layouts/ad-preview.marko.js +2 -2
- package/components/menu-toggle-button.marko.js +2 -2
- package/components/native-x/render.marko.js +2 -2
- package/components/nodes/author-bio.marko.js +2 -2
- package/components/nodes/author-card.marko.js +2 -2
- package/components/nodes/author-published.marko.js +2 -2
- package/components/nodes/author.marko.js +2 -2
- package/components/nodes/components/attribution.marko.js +2 -2
- package/components/nodes/components/authors.marko.js +2 -2
- package/components/nodes/components/company-name.marko.js +2 -2
- package/components/nodes/components/dates.marko.js +2 -2
- package/components/nodes/components/section-sponsor.marko.js +2 -2
- package/components/nodes/content-published.marko.js +2 -2
- package/components/nodes/content.marko.js +2 -2
- package/components/nodes/directory-section-feed-content.marko.js +2 -2
- package/components/nodes/featured-products-card.marko.js +2 -2
- package/components/nodes/product-feed-content.marko.js +2 -2
- package/components/nodes/section-feed-content.marko.js +2 -2
- package/components/nodes/standard-promo.marko.js +2 -2
- package/components/nodes/youtube-card.marko.js +2 -2
- package/components/page/attribution.marko.js +2 -2
- package/components/page/contact-details.marko.js +2 -2
- package/components/page/content.marko.js +2 -2
- package/components/page/contents.marko.js +2 -2
- package/components/page/dates.marko.js +2 -2
- package/components/page/default.marko.js +2 -2
- package/components/page/website-section.marko.js +2 -2
- package/components/pagination-controls.marko.js +2 -2
- package/components/query-total-count.marko.js +2 -2
- package/components/reveal-ad-handler.marko.js +2 -2
- package/components/site-footer/container.marko.js +2 -2
- package/components/site-footer/copyright.marko.js +2 -2
- package/components/site-footer/index.marko.js +2 -2
- package/components/site-footer/social-icons.marko.js +2 -2
- package/components/site-header.marko.js +2 -2
- package/components/site-menu/components/search.marko.js +2 -2
- package/components/site-menu/components/social-icons.marko.js +2 -2
- package/components/site-menu/header.marko.js +2 -2
- package/components/site-menu/index.marko.js +2 -2
- package/components/site-menu/section.marko.js +2 -2
- package/components/site-navbar/brand.marko.js +2 -2
- package/components/site-navbar/index.marko.js +2 -2
- package/components/site-navbar/item.marko.js +2 -2
- package/components/site-navbar/items.marko.js +2 -2
- package/components/site-navbar/logo.marko.js +2 -2
- package/components/site-navbar/search.marko.js +2 -2
- package/components/site-newsletter-menu.marko.js +2 -2
- package/components/social/icon-link.marko.js +2 -2
- package/components/social/icons.marko.js +2 -2
- package/components/social-follow.marko.js +2 -2
- package/components/sponsored-label-logo.marko.js +2 -2
- package/components/sponsored-section-logo.marko.js +2 -2
- package/components/sponsored-section-name-logo.marko.js +2 -2
- package/components/ssr-html-inject.marko.js +2 -2
- package/package.json +2 -2
- package/templates/ad-preview.marko.js +2 -2
- package/templates/content/components/presented-by.marko.js +2 -2
- package/templates/content/native-x-story.marko.js +2 -2
- package/templates/content/print.marko.js +2 -2
- package/templates/email-preview.marko.js +2 -2
- package/templates/search.marko.js +2 -2
- package/templates/taxonomy.marko.js +2 -2
package/I18N_GUIDE.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# Internationalization (i18n) Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`marko-web-theme-monorail` supports internationalization through an `i18n` function that must be provided by consuming sites. This guide explains how the theme handles translations and how to contribute components that respect multilingual requirements.
|
|
6
|
+
|
|
7
|
+
## Core Principle
|
|
8
|
+
|
|
9
|
+
**Any hardcoded user-facing text in theme components MUST be wrapped with the i18n function.** This ensures sites can translate UI text without modifying the theme package itself.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
### For Sites (Consuming Projects)
|
|
16
|
+
|
|
17
|
+
Sites provide an i18n function that maps English strings to their translated equivalents:
|
|
18
|
+
|
|
19
|
+
**Example: `sites/mundopmmi.com/config/i18n.js`**
|
|
20
|
+
```javascript
|
|
21
|
+
module.exports = {
|
|
22
|
+
"We hope you've enjoyed your articles.": "Esperamos haya disfrutado sus artículos.",
|
|
23
|
+
"Create a free account": "Cree una cuenta gratuita",
|
|
24
|
+
"account to continue reading": "cuenta gratuita para continuar leyendo",
|
|
25
|
+
// ... hundreds more translations
|
|
26
|
+
};
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then in the site's start-server call:
|
|
30
|
+
```javascript
|
|
31
|
+
// sites/mundopmmi.com/index.js
|
|
32
|
+
const i18n = require('./config/i18n');
|
|
33
|
+
|
|
34
|
+
module.exports = startServer({
|
|
35
|
+
// ... config ...
|
|
36
|
+
i18n: (v) => i18n[`${v}`.toLowerCase()] || v,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This makes `out.global.i18n` available to all theme components.
|
|
41
|
+
|
|
42
|
+
### For Theme Components
|
|
43
|
+
|
|
44
|
+
#### Marko Components (Server-side)
|
|
45
|
+
|
|
46
|
+
1. **Destructure i18n with a fallback:**
|
|
47
|
+
```marko
|
|
48
|
+
$ const { i18n } = out.global;
|
|
49
|
+
$ const i18nFunc = typeof i18n === 'function' ? i18n : (v) => v;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
2. **Wrap user-facing strings:**
|
|
53
|
+
```marko
|
|
54
|
+
$ const title = i18nFunc("We hope you've enjoyed your articles.");
|
|
55
|
+
$ const buttonLabel = i18nFunc("Create a free account");
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
3. **Handle dynamic content carefully:**
|
|
59
|
+
```marko
|
|
60
|
+
// For singular/plural variants:
|
|
61
|
+
$ const remaining = viewLimit - views;
|
|
62
|
+
$ let message;
|
|
63
|
+
$ if (remaining === 1) {
|
|
64
|
+
$ message = i18nFunc("You have 1 article view remaining.");
|
|
65
|
+
$ } else {
|
|
66
|
+
$ // Use placeholder for numbers
|
|
67
|
+
$ message = i18nFunc("You have {count} article views remaining.");
|
|
68
|
+
$ message = message.replace('{count}', remaining);
|
|
69
|
+
$ }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Vue/Browser Components (Client-side)
|
|
73
|
+
|
|
74
|
+
For client-side Vue components, translations must be passed as props from the Marko template:
|
|
75
|
+
|
|
76
|
+
**In Marko template:**
|
|
77
|
+
```marko
|
|
78
|
+
$ const { i18n } = out.global;
|
|
79
|
+
$ const i18nFunc = typeof i18n === 'function' ? i18n : (v) => v;
|
|
80
|
+
|
|
81
|
+
$ const locale = {
|
|
82
|
+
"Sign up": i18nFunc("Sign up"),
|
|
83
|
+
"Email address": i18nFunc("Email address"),
|
|
84
|
+
"Subscribe": i18nFunc("Subscribe"),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
<marko-web-browser-component
|
|
88
|
+
name="InlineNewsletterForm"
|
|
89
|
+
props={ locale, defaultNewsletter, ... }
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**In Vue component:**
|
|
94
|
+
```vue
|
|
95
|
+
<template>
|
|
96
|
+
<form @submit="onSubmit">
|
|
97
|
+
<label>{{ locale['Email address'] || 'Email address' }}</label>
|
|
98
|
+
<input type="email" placeholder="user@example.com" />
|
|
99
|
+
<button type="submit">{{ locale['Subscribe'] || 'Subscribe' }}</button>
|
|
100
|
+
</form>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<script>
|
|
104
|
+
export default {
|
|
105
|
+
props: {
|
|
106
|
+
locale: {
|
|
107
|
+
type: Object,
|
|
108
|
+
default: () => ({}),
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
methods: {
|
|
112
|
+
onSubmit() {
|
|
113
|
+
// ...
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
</script>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Best Practices
|
|
123
|
+
|
|
124
|
+
### 1. Use Simple, Descriptive English Strings
|
|
125
|
+
|
|
126
|
+
✅ **Good:**
|
|
127
|
+
```marko
|
|
128
|
+
$ const label = i18nFunc("Create a free account");
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
❌ **Bad:**
|
|
132
|
+
```marko
|
|
133
|
+
$ const label = i18nFunc("Create Account"); // Too vague, hard to translate consistently
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2. Avoid Inline Interpolation with Numbers
|
|
137
|
+
|
|
138
|
+
✅ **Good:**
|
|
139
|
+
```marko
|
|
140
|
+
$ const template = i18nFunc("You have {count} article views remaining.");
|
|
141
|
+
$ const message = template.replace('{count}', remaining);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
❌ **Bad:**
|
|
145
|
+
```marko
|
|
146
|
+
$ const message = i18nFunc(`You have ${remaining} article views remaining.`);
|
|
147
|
+
// Translators can't translate this—the number is embedded
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 3. Keep Related Strings Together
|
|
151
|
+
|
|
152
|
+
✅ **Good:**
|
|
153
|
+
```marko
|
|
154
|
+
$ const createLabel = i18nFunc("Create a free");
|
|
155
|
+
$ const accountLabel = i18nFunc("account to continue reading");
|
|
156
|
+
$ const message = `${createLabel} <strong>${siteName}</strong> ${accountLabel}`;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This allows translators to reorder the sentence structure if needed.
|
|
160
|
+
|
|
161
|
+
❌ **Bad:**
|
|
162
|
+
```marko
|
|
163
|
+
$ const message = i18nFunc(`Create a free ${siteName} account to continue reading`);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 4. Always Provide a Fallback
|
|
167
|
+
|
|
168
|
+
Always check if i18n exists and fall back to English:
|
|
169
|
+
|
|
170
|
+
```marko
|
|
171
|
+
$ const i18nFunc = typeof i18n === 'function' ? i18n : (v) => v;
|
|
172
|
+
$ const label = i18nFunc("Your label");
|
|
173
|
+
// If i18n doesn't exist, label will be "Your label" (English)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This ensures backward compatibility with sites that don't have an i18n config.
|
|
177
|
+
|
|
178
|
+
### 5. Document Required Translation Strings
|
|
179
|
+
|
|
180
|
+
When adding new user-facing text, document it in the component or in a comment:
|
|
181
|
+
|
|
182
|
+
```marko
|
|
183
|
+
<!--
|
|
184
|
+
Required translation strings for this component:
|
|
185
|
+
- "We hope you've enjoyed your articles."
|
|
186
|
+
- "You have 1 article view remaining."
|
|
187
|
+
- "You have {count} article views remaining."
|
|
188
|
+
- "This is your last free article."
|
|
189
|
+
- "Enjoy this free article."
|
|
190
|
+
- "Create a free account"
|
|
191
|
+
- "Create a free"
|
|
192
|
+
- "account to continue reading"
|
|
193
|
+
-->
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Or add a comment in your PR describing what strings need to be added to site i18n configs.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Example: Adding i18n to a Component
|
|
201
|
+
|
|
202
|
+
### Before (No i18n)
|
|
203
|
+
```marko
|
|
204
|
+
$ const title = "View All Products";
|
|
205
|
+
$ const emptyMessage = "No products found.";
|
|
206
|
+
|
|
207
|
+
<div class="products">
|
|
208
|
+
<h2>${title}</h2>
|
|
209
|
+
<if(!products.length)>
|
|
210
|
+
<p>${emptyMessage}</p>
|
|
211
|
+
</if>
|
|
212
|
+
</div>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### After (With i18n)
|
|
216
|
+
```marko
|
|
217
|
+
$ const { i18n } = out.global;
|
|
218
|
+
$ const i18nFunc = typeof i18n === 'function' ? i18n : (v) => v;
|
|
219
|
+
|
|
220
|
+
$ const title = i18nFunc("View All Products");
|
|
221
|
+
$ const emptyMessage = i18nFunc("No products found.");
|
|
222
|
+
|
|
223
|
+
<div class="products">
|
|
224
|
+
<h2>${title}</h2>
|
|
225
|
+
<if(!products.length)>
|
|
226
|
+
<p>${emptyMessage}</p>
|
|
227
|
+
</if>
|
|
228
|
+
</div>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Site Implementation
|
|
232
|
+
Sites using the component add translations to their i18n config:
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
// sites/example-spanish/config/i18n.js
|
|
236
|
+
module.exports = {
|
|
237
|
+
"View All Products": "Ver Todos los Productos",
|
|
238
|
+
"No products found.": "No se encontraron productos.",
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Handling Missing Translations
|
|
245
|
+
|
|
246
|
+
If a site doesn't have a translation in its i18n config, the fallback behavior is:
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
// sites/example-spanish/config/i18n.js
|
|
250
|
+
i18n: (v) => i18n[`${v}`.toLowerCase()] || v
|
|
251
|
+
// ^ returns English if not found
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
This means:
|
|
255
|
+
- ✅ Sites will display in English if a translation is missing
|
|
256
|
+
- ✅ No runtime errors
|
|
257
|
+
- ✅ Graceful degradation
|
|
258
|
+
- ⚠️ Encourages sites to keep their i18n configs up-to-date with new theme versions
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Release Notes Template
|
|
263
|
+
|
|
264
|
+
When releasing a new version with i18n changes, include:
|
|
265
|
+
|
|
266
|
+
```markdown
|
|
267
|
+
## vX.Y.Z
|
|
268
|
+
|
|
269
|
+
### New Features
|
|
270
|
+
- Added internationalization support to `content-meter` block component
|
|
271
|
+
|
|
272
|
+
### i18n Changes (⚠️ Action Required for Multilingual Sites)
|
|
273
|
+
The following new translation strings are required:
|
|
274
|
+
|
|
275
|
+
**content-meter.marko:**
|
|
276
|
+
- `We hope you've enjoyed your articles.`
|
|
277
|
+
- `You have 1 article view remaining.`
|
|
278
|
+
- `You have {count} article views remaining.`
|
|
279
|
+
- `This is your last free article.`
|
|
280
|
+
- `Enjoy this free article.`
|
|
281
|
+
- `Create a free account`
|
|
282
|
+
- `Create a free`
|
|
283
|
+
- `account to continue reading`
|
|
284
|
+
|
|
285
|
+
**Action:** Add these strings to your site's `config/i18n.js` file and provide translations.
|
|
286
|
+
|
|
287
|
+
See [I18N_GUIDE.md](./I18N_GUIDE.md) for details.
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Common Questions
|
|
293
|
+
|
|
294
|
+
### Q: What if my site doesn't have multilingual requirements?
|
|
295
|
+
A: You don't need to do anything. The i18n function is optional, and the theme will fall back to English text automatically.
|
|
296
|
+
|
|
297
|
+
### Q: Can I override a translation string in my site?
|
|
298
|
+
A: Yes. Your site's i18n config is the source of truth. Add (or modify) a translation string and it will be used instead of the theme's default English.
|
|
299
|
+
|
|
300
|
+
### Q: Should all text go through i18n, or just user-facing labels?
|
|
301
|
+
A: Only **user-facing text** should be translated. Technical strings (class names, data attributes, IDs, etc.) should not.
|
|
302
|
+
|
|
303
|
+
✅ Translate:
|
|
304
|
+
- Button labels
|
|
305
|
+
- Form placeholders
|
|
306
|
+
- Error messages
|
|
307
|
+
- Section titles
|
|
308
|
+
- Empty state messages
|
|
309
|
+
|
|
310
|
+
❌ Don't translate:
|
|
311
|
+
- CSS class names
|
|
312
|
+
- Data attributes
|
|
313
|
+
- HTML IDs
|
|
314
|
+
- Variable names
|
|
315
|
+
- Code comments
|
|
316
|
+
|
|
317
|
+
### Q: What about accessibility strings (aria-labels)?
|
|
318
|
+
A: Yes, translate these too. They're user-facing for screen readers.
|
|
319
|
+
|
|
320
|
+
```marko
|
|
321
|
+
$ const ariaLabel = i18nFunc("Content Meter");
|
|
322
|
+
<div role="region" aria-label=ariaLabel>...</div>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Q: How do I handle pluralization in other languages?
|
|
326
|
+
A: Use placeholder strings and handle pluralization at the template level:
|
|
327
|
+
|
|
328
|
+
```marko
|
|
329
|
+
$ const count = items.length;
|
|
330
|
+
$ let message;
|
|
331
|
+
$ if (count === 0) {
|
|
332
|
+
$ message = i18nFunc("No items");
|
|
333
|
+
$ } else if (count === 1) {
|
|
334
|
+
$ message = i18nFunc("1 item");
|
|
335
|
+
$ } else {
|
|
336
|
+
$ message = i18nFunc("{count} items").replace('{count}', count);
|
|
337
|
+
$ }
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Then sites provide translations for each variant:
|
|
341
|
+
```javascript
|
|
342
|
+
{
|
|
343
|
+
"No items": "Sin artículos",
|
|
344
|
+
"1 item": "1 artículo",
|
|
345
|
+
"{count} items": "{count} artículos",
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## References
|
|
352
|
+
|
|
353
|
+
- [marko-web-theme-monorail Repository](https://github.com/parameter1/mindful-web/tree/main/packages/marko-web-theme-monorail)
|
|
354
|
+
- [Consuming Site Example: mundopmmi](https://github.com/parameter1/pmmi-media-group-websites/tree/main/sites/mundopmmi.com)
|
package/README.md
CHANGED
|
@@ -101,3 +101,11 @@ $ let body = content.body;
|
|
|
101
101
|
$ // ...
|
|
102
102
|
|
|
103
103
|
```
|
|
104
|
+
|
|
105
|
+
## Internationalization (i18n)
|
|
106
|
+
|
|
107
|
+
This theme supports internationalization for components. See [I18N_GUIDE.md](./I18N_GUIDE.md) for:
|
|
108
|
+
- How to add i18n support to new components
|
|
109
|
+
- Best practices for translatable strings
|
|
110
|
+
- Examples for both Marko and Vue components
|
|
111
|
+
- Common questions and answers
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/ads/fixed-ad-bottom.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
marko_assign = require("marko/dist/runtime/helpers/assign"),
|
|
8
8
|
marko_web_gam_fixed_ad_bottom_template = require("@mindful-web/marko-web-gam/components/fixed-ad-bottom.marko"),
|
|
@@ -29,7 +29,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
marko_template.meta = {
|
|
32
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
32
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/ads/fixed-ad-bottom.marko",
|
|
33
33
|
tags: [
|
|
34
34
|
"@mindful-web/marko-web-gam/components/fixed-ad-bottom.marko"
|
|
35
35
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/ads/reveal-ad-handler.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
marko_web_browser_component_template = require("@mindful-web/marko-web/components/browser-component.marko"),
|
|
8
8
|
marko_loadTag = require("marko/dist/runtime/helpers/load-tag"),
|
|
@@ -31,7 +31,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
marko_template.meta = {
|
|
34
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
34
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/ads/reveal-ad-handler.marko",
|
|
35
35
|
tags: [
|
|
36
36
|
"@mindful-web/marko-web/components/browser-component.marko"
|
|
37
37
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/author-bios.marko",
|
|
6
6
|
marko_component = require("./author-bios.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
|
|
@@ -67,7 +67,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
67
67
|
}, marko_component);
|
|
68
68
|
|
|
69
69
|
marko_template.meta = {
|
|
70
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
70
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/author-bios.marko",
|
|
71
71
|
component: "./author-bios.marko",
|
|
72
72
|
tags: [
|
|
73
73
|
"../nodes/author-bio.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/callout-cards.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
8
8
|
objectPath_module = module_objectPath_module.default || module_objectPath_module,
|
|
@@ -66,7 +66,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
marko_template.meta = {
|
|
69
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
69
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/callout-cards.marko",
|
|
70
70
|
tags: [
|
|
71
71
|
"@mindful-web/marko-web/components/element/index.marko",
|
|
72
72
|
"@mindful-web/marko-web/components/element/block.marko"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/company-inquiry-form.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
|
|
8
8
|
defaultValue = module_defaultValue.default || module_defaultValue,
|
|
@@ -83,7 +83,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
marko_template.meta = {
|
|
86
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
86
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/company-inquiry-form.marko",
|
|
87
87
|
tags: [
|
|
88
88
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
89
89
|
"@mindful-web/marko-web-p1-events/components/track-inquiry-submission.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/company-search.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
marko_web_browser_component_template = require("@mindful-web/marko-web/components/browser-component.marko"),
|
|
8
8
|
marko_loadTag = require("marko/dist/runtime/helpers/load-tag"),
|
|
@@ -22,7 +22,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
marko_template.meta = {
|
|
25
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
25
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/company-search.marko",
|
|
26
26
|
tags: [
|
|
27
27
|
"@mindful-web/marko-web/components/browser-component.marko"
|
|
28
28
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/content-card-deck.marko",
|
|
6
6
|
marko_component = require("./content-card-deck.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_utils_module = require("@mindful-web/marko-web/utils"),
|
|
@@ -317,7 +317,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
317
317
|
}, marko_component);
|
|
318
318
|
|
|
319
319
|
marko_template.meta = {
|
|
320
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
320
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/content-card-deck.marko",
|
|
321
321
|
component: "./content-card-deck.marko",
|
|
322
322
|
tags: [
|
|
323
323
|
"@mindful-web/marko-web/components/element/link.marko",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import defaultValue from "@mindful-web/marko-core/utils/default-value";
|
|
2
2
|
import baseContentEntity from "@mindful-web/marko-web-p1-events/utils/base-content-entity";
|
|
3
3
|
|
|
4
|
-
$ const { config, site, contentMeterState, req } = out.global;
|
|
4
|
+
$ const { config, site, contentMeterState, req, i18n } = out.global;
|
|
5
5
|
$ const { content } = input;
|
|
6
6
|
$ const {
|
|
7
7
|
displayGate,
|
|
@@ -24,20 +24,38 @@ $ const additionalEventData = {
|
|
|
24
24
|
...(contentEntity && { ent: contentEntity }),
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
// Fallback to identity function if i18n is not available (backward compatibility)
|
|
28
|
+
$ const i18nFunc = typeof i18n === ‘function’ ? i18n : (v) => v;
|
|
28
29
|
|
|
29
30
|
$ const setTextVariables = () => {
|
|
30
31
|
// Set title based on views remaining
|
|
31
|
-
let dynamicTitle = defaultValue(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
let dynamicTitle = defaultValue(
|
|
33
|
+
input.dynamicTitle,
|
|
34
|
+
i18nFunc("We hope you’ve enjoyed your articles.")
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (views < viewLimit) {
|
|
38
|
+
const remaining = viewLimit - views;
|
|
39
|
+
if (remaining === 1) {
|
|
40
|
+
dynamicTitle = i18nFunc("You have 1 article view remaining.");
|
|
41
|
+
} else {
|
|
42
|
+
const template = i18nFunc("You have {count} article views remaining.");
|
|
43
|
+
dynamicTitle = template.replace(‘{count}’, remaining);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (viewLimit === views && !displayOverlay) {
|
|
48
|
+
dynamicTitle = i18nFunc("This is your last free article.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (viewLimit === 1 && !displayOverlay) {
|
|
52
|
+
dynamicTitle = i18nFunc("Enjoy this free article.");
|
|
53
|
+
}
|
|
36
54
|
|
|
37
55
|
return {
|
|
38
56
|
title: dynamicTitle,
|
|
39
|
-
collapsedTitle: "Create a free account",
|
|
40
|
-
callToAction:
|
|
57
|
+
collapsedTitle: i18nFunc("Create a free account"),
|
|
58
|
+
callToAction: `${i18nFunc("Create a free")} <span class="content-meter__call-to-action--site-name">${config.siteName()}</span> ${i18nFunc("account to continue reading")}`,
|
|
41
59
|
};
|
|
42
60
|
}
|
|
43
61
|
$ const textVariables = setTextVariables();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/content-meter.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
|
|
8
8
|
defaultValue = module_defaultValue.default || module_defaultValue,
|
|
@@ -21,7 +21,7 @@ var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
|
21
21
|
function render(input, out, __component, component, state) {
|
|
22
22
|
var data = input;
|
|
23
23
|
|
|
24
|
-
const { config, site, contentMeterState, req } = out.global;
|
|
24
|
+
const { config, site, contentMeterState, req, i18n } = out.global;
|
|
25
25
|
|
|
26
26
|
const { content } = input;
|
|
27
27
|
|
|
@@ -46,18 +46,37 @@ function render(input, out, __component, component, state) {
|
|
|
46
46
|
...(contentEntity && { ent: contentEntity }),
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
const i18nFunc = typeof i18n === ‘function’ ? i18n : (v) => v;
|
|
50
|
+
|
|
49
51
|
const setTextVariables = () => {
|
|
50
52
|
// Set title based on views remaining
|
|
51
|
-
let dynamicTitle = defaultValue(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
let dynamicTitle = defaultValue(
|
|
54
|
+
input.dynamicTitle,
|
|
55
|
+
i18nFunc("We hope you’ve enjoyed your articles.")
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (views < viewLimit) {
|
|
59
|
+
const remaining = viewLimit - views;
|
|
60
|
+
if (remaining === 1) {
|
|
61
|
+
dynamicTitle = i18nFunc("You have 1 article view remaining.");
|
|
62
|
+
} else {
|
|
63
|
+
const template = i18nFunc("You have {count} article views remaining.");
|
|
64
|
+
dynamicTitle = template.replace(‘{count}’, remaining);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (viewLimit === views && !displayOverlay) {
|
|
69
|
+
dynamicTitle = i18nFunc("This is your last free article.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (viewLimit === 1 && !displayOverlay) {
|
|
73
|
+
dynamicTitle = i18nFunc("Enjoy this free article.");
|
|
74
|
+
}
|
|
56
75
|
|
|
57
76
|
return {
|
|
58
77
|
title: dynamicTitle,
|
|
59
|
-
collapsedTitle: "Create a free account",
|
|
60
|
-
callToAction:
|
|
78
|
+
collapsedTitle: i18nFunc("Create a free account"),
|
|
79
|
+
callToAction: `${i18nFunc("Create a free")} <span class="content-meter__call-to-action--site-name">${config.siteName()}</span> ${i18nFunc("account to continue reading")}`,
|
|
61
80
|
};
|
|
62
81
|
}
|
|
63
82
|
|
|
@@ -135,7 +154,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
135
154
|
});
|
|
136
155
|
|
|
137
156
|
marko_template.meta = {
|
|
138
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
157
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/content-meter.marko",
|
|
139
158
|
tags: [
|
|
140
159
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
141
160
|
"../menu-toggle-button.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/full-width-native-ad.marko",
|
|
6
6
|
marko_component = require("./full-width-native-ad.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -52,7 +52,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
52
52
|
}, marko_component);
|
|
53
53
|
|
|
54
54
|
marko_template.meta = {
|
|
55
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
55
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/full-width-native-ad.marko",
|
|
56
56
|
component: "./full-width-native-ad.marko",
|
|
57
57
|
tags: [
|
|
58
58
|
"../nodes/section-feed-content.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/geo-summary.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
8
8
|
objectPath_module = module_objectPath_module.default || module_objectPath_module,
|
|
@@ -150,7 +150,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
marko_template.meta = {
|
|
153
|
-
id: "/@mindful-web/marko-web-theme-monorail$1.
|
|
153
|
+
id: "/@mindful-web/marko-web-theme-monorail$1.81.0/components/blocks/geo-summary.marko",
|
|
154
154
|
tags: [
|
|
155
155
|
"../menu-toggle-button.marko",
|
|
156
156
|
"@mindful-web/marko-web/components/element/index.marko",
|