ワンクリックで
add-new-language
Complete workflow for adding a new language to the Betizen SSG, from data setup through translating a starter casino.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Complete workflow for adding a new language to the Betizen SSG, from data setup through translating a starter casino.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Complete workflow for creating a new blog post across all supported languages in the Betizen SSG. Asks for article content in Spanish, then creates all language variants with correct currency conversions, internal links, affiliate tracking, and featured images.
Translate casino, forex, or binary entity reviews between any language pair. Handles internal links, currency conversion, cross-sell cards, and recursive translation of linked content.
SOC 職業分類に基づく
| name | add-new-language |
| description | Complete workflow for adding a new language to the Betizen SSG, from data setup through translating a starter casino. |
Adding a new language involves:
_data/l10n/ and navigation in _data/nav.js/content/{lang}/ directory with .11tydata.js filesRelated skills: This skill focuses on language infrastructure and casino translation. For reputation codes, score guidelines, blacklist rules, and frontmatter schema conventions, see the content-guidelines skill.
This skill is generic for any language. Determine these values before beginning:
{lang} — language code (e.g., cs for Czech, fr for French){lang_name} — how the language says its own name (e.g., Čeština, Français, Deutsch){flag_path} — the flag SVG path (e.g., cz.svg, fr.svg, de.svg){url_prefix} — URL path prefix, usually /{lang}/Then validate:
# Check the flag SVG exists
ls public/imgs/flags/svg/{flag_path}
# If missing, download or create it
Determine content path patterns by examining existing non-default languages:
# See existing language URL paths for reference
ls _site/en/
ls _site/pt-br/
There are two patterns based on whether the language is the default or not:
| Aspect | Default (es) | Non-default (en, pt-br, new {lang}) |
|---|---|---|
| Homepage URL | / | /{url_prefix}/ |
| Casino URL | /casino/{slug}/ | /{url_prefix}/{casino_slug}/{slug}/ |
| Casino list | /casinos/ | /{url_prefix}/{casino_list_slug}/ |
locale_links | Links from default | Links TO default and others |
.11tydata.js permalink | /{slug}/ | LANG + "/{section}/" + this.slugify(data.slugOverride) + "/" |
This is controlled by eleventy.config.js:
eleventyConfig.addPlugin(EleventyI18nPlugin, {
defaultLanguage: "es",
errorMode: "never",
});
All content pages in non-default languages must set lang to the language code in their .11tydata.js file. The locale_url and locale_links filters (from EleventyI18nPlugin) automatically map URLs between languages based on matching content paths.
ls public/imgs/flags/svg/{flag_path}
# If missing, copy from an existing SVG or add the flag
cp public/imgs/flags/svg/un.svg public/imgs/flags/svg/{flag_path}
_data/l10n/{lang}.jsThis is the centralized translation file. Model it on _data/l10n/en.js or _data/l10n/cs.js. All strings must be translated:
const metadata = require("../metadata.js");
module.exports = {
footerNav: {
blog: { name: "{translated_blog}", slug: "/{url_prefix}/{blog_list_slug}/" },
games: { name: "{translated_games}", slug: "/{url_prefix}/{games_list_slug}/" },
},
menu: {
name: "{lang_name}",
flagClass: "flag-icon-{flag_code}",
flagSvg: "/assets/imgs/flags/svg/{flag_path}",
},
top: {
text: "{translated_add_site_text}",
url: "https://forms.gle/vMpnv4cFgccHYNp56",
},
promo: {
// Copy from existing language — banners, CTAs, casino promos
// Translate CTA text, update image references if language-specific banners exist
},
categories: {
slot: { name: "{translated_slots}", url: "/{url_prefix}/{games_slug}/{slot_slug}/" },
bingo: { name: "{translated_bingo}", url: "/{url_prefix}/{games_slug}/{bingo_slug}/" },
roulette: { name: "{translated_roulette}", url: "/{url_prefix}/{games_slug}/{roulette_slug}/" },
blackjack: { name: "{translated_blackjack}", url: "/{url_prefix}/{games_slug}/{blackjack_slug}/" },
instant: { name: "{translated_instant_win}", url: "/{url_prefix}/{games_slug}/{instant_slug}/" },
baccarat: { name: "{translated_baccarat}", url: "/{url_prefix}/{games_slug}/{baccarat_slug}/" },
poker: { name: "{translated_video_poker}", url: "/{url_prefix}/{games_slug}/{poker_slug}/" },
},
texts: {
// All ~150 translatable strings — copy all keys from an existing l10n file
// Common keys: readMore, visit, bonus, login, logout, search, reputation, karma,
// voting, reputation.fair, reputation.acceptable, reputation.caution, reputation.dangerous,
// slogan.firstLine, slogan.secondLine, etc.
},
};
Critical: All keys present in other _data/l10n/{lang}.js files must exist. Missing keys will cause rendering errors. The texts object should be a complete copy — translate string values, keep boilerplate/technical content unchanged.
Copy-paste tip: Duplicate an existing _data/l10n/{lang}.js file, then translate each string value. Leave technical fields unchanged (e.g., promo.url, image paths).
_data/languages.jsAdd a require line for the new language:
module.exports = {
es: require("./l10n/es.js"),
"pt-br": require("./l10n/pt-br.js"),
en: require("./l10n/en.js"),
cs: require("./l10n/cs.js"),
"{lang}": require("./l10n/{lang}.js"),
};
_data/nav.js{lang}: [
{ text: "{translated_casinos}", url: "/{url_prefix}/{casino_list_slug}/", id: "term-id-5" },
{ text: "{translated_crypto}", url: "/{url_prefix}/{crypto_slug}/", id: "term-crypto" },
{ text: "Forex", url: "/{url_prefix}/forex/", id: "term-forex" },
{ text: "{translated_binary}", url: "/{url_prefix}/{binary_slug}/", id: "term-binary" },
],
The id field must match existing IDs from other languages.
_data/categories.js reads from languages.js automatically. Add a new entry:
module.exports = function () {
return {
es: buildGameCategoriesFromLanguage("es"),
"pt-br": buildGameCategoriesFromLanguage("pt-br"),
en: buildGameCategoriesFromLanguage("en"),
"{lang}": buildGameCategoriesFromLanguage("{lang}"),
};
};
The flag SVG mapping is currently hardcoded as if/elif/else in 3 template files:
| File | Lines |
|---|---|
_includes/components/bz-message.njk | 69-75 |
_includes/locale-fix.njk | 15-21 |
_includes/templates/languages-modal.template.njk | 9-15 |
Each has a pattern like:
{% if languages[link.lang].menu.flagClass == "flag-icon-es" %}
<img src="/assets/imgs/flags/svg/ar.svg" ... />
{% elif languages[link.lang].menu.flagClass == "flag-icon-br" %}
<img src="/assets/imgs/flags/svg/br.svg" ... />
{% else %}
<img src="/assets/imgs/flags/svg/un.svg" ... />
{% endif %}
Minimal approach: Add an elif branch for the new flagClass in all 3 files:
{% elif languages[link.lang].menu.flagClass == "flag-icon-{lang}" %}
<img src="/assets/imgs/flags/svg/{flag_path}" ... />
Recommended approach — refactor to data-driven: Since you already added flagSvg to _data/l10n/{lang}.js in Step 2, all 3 templates can be simplified to a single line:
<img src="{{ languages[link.lang].menu.flagSvg }}" class="..." alt="{{ languages[link.lang].menu.name }}" />
This makes all future language additions pure data changes — no template edits needed.
Create content/{lang}/ with the following files and subdirectories:
{lang}.11tydata.js (root directory data — uses closure pattern):
module.exports = function () {
var LANG = "{lang}";
return {
lang: LANG,
layout: "layouts/base.njk",
permalink: function (data) {
if (data.slugOverride) {
return "/" + LANG + "/" + this.slugify(data.slugOverride) + "/";
}
},
};
};
index.njk (home page — translate from existing language):
---
title: "{translated_title}"
layout: layouts/home.njk
---
casinos/casinos/casinos.11tydata.js (casino directory data — uses closure pattern):
const metadata = require("../../../_data/metadata.js");
const languages = require("../../../_data/languages.js");
module.exports = function () {
var LANG = "{lang}";
return {
lang: LANG,
tags: ["casinos"],
layout: "layouts/single-casino.njk",
permalink: function (data) {
if (data.slugOverride) {
return LANG + "/{casino_slug}/" + this.slugify(data.slugOverride) + "/";
}
},
eleventyComputed: {
pageTitle: (data) => {
return data.title + " | {review_label} - " + metadata.title;
},
bonus: {
link: (data) => {
if (!data.bonus.link && data.page.lang) {
return languages[data.page.lang].promo.url;
} else {
return data.bonus.link;
}
},
},
},
};
};
Where {casino_slug} is the URL path segment for casino details (e.g., online-casino for EN, cassino for PT-BR, online-kasino for CS) and {review_label} is "Honest review" in the target language.
casinos/casinos.njk (casino listing page — uses explicit permalink, not slugOverride):
---
title: "{translated_title}"
description: "{translated_description}"
permalink: /{url_prefix}/{casino_list_slug}/
eleventyComputed:
pageTitle: "{translated_page_title} - {{metadata.title}}"
layout: layouts/casinos.njk
---
Where {casino_list_slug} is the listing page slug (e.g., online-casinos for EN, cassinos for PT-BR).
casinos/blacklisted.njk (blacklist page — uses explicit permalink, layout is layouts/base.njk):
---
title: "{translated_title}"
description: "{translated_description}"
layout: "layouts/base.njk"
permalink: "/{url_prefix}/{casino_list_slug}/blacklist/"
---
pages/pages/pages.11tydata.js (static pages — uses closure pattern):
module.exports = function () {
var LANG = "{lang}";
return {
lang: LANG,
tags: ["page"],
layout: "layouts/page.njk",
permalink: function (data) {
if (data.slugOverride) {
return LANG + "/" + this.slugify(data.slugOverride) + "/";
}
},
};
};
Create individual pages: about.njk, benefits.njk (slugOverride: vyhody), glossary.njk, leaderboard.njk, affiliate-programs.njk, bonus.njk, transparency.njk, etc.
blog/blog/blog.11tydata.js (blog posts — uses closure pattern):
module.exports = function () {
var LANG = "{lang}";
return {
lang: LANG,
tags: ["posts"],
layout: "layouts/single-post.njk",
permalink: function (data) {
if (data.slugOverride) {
return LANG + "/{blog_slug}/" + this.slugify(data.slugOverride) + "/";
}
},
};
};
Where {blog_slug} is the URL path segment for blog (e.g., blog for EN, clanky for CS).
blog/archive.njk (blog listing page — uses explicit permalink):
---
title: "{translated_title}"
description: "{translated_description}"
permalink: /{url_prefix}/{blog_list_slug}/
eleventyComputed:
pageTitle: "{translated_page_title} - {{metadata.title}}"
layout: layouts/blog.njk
---
forex/forex/forex.11tydata.js (forex brokers — uses closure pattern):
const metadata = require("../../../_data/metadata.js");
const languages = require("../../../_data/languages.js");
module.exports = function () {
var LANG = "{lang}";
return {
lang: LANG,
tags: ["forex"],
layout: "layouts/single-forex.njk",
permalink: function (data) {
if (data.slugOverride) {
return LANG + "/forex/" + this.slugify(data.slugOverride) + "/";
}
},
eleventyComputed: {
pageTitle: (data) => {
return data.title + " | {review_label} - " + metadata.title;
},
bonus: {
link: (data) => {
if (!data.bonus.link && data.page.lang) {
return languages[data.page.lang].promo.url;
} else {
return data.bonus.link;
}
},
},
},
};
};
binaries/binaries/binaries.11tydata.js (binary options — uses closure pattern):
const metadata = require("../../../_data/metadata.js");
const languages = require("../../../_data/languages.js");
module.exports = function () {
var LANG = "{lang}";
return {
lang: LANG,
tags: ["binaries"],
layout: "layouts/single-binary.njk",
permalink: function (data) {
if (data.slugOverride) {
return LANG + "/{binary_slug}/" + this.slugify(data.slugOverride) + "/";
}
},
eleventyComputed: {
pageTitle: (data) => {
return data.title + " | {review_label} - " + metadata.title;
},
bonus: {
link: (data) => {
if (!data.bonus.link && data.page.lang) {
return languages[data.page.lang].promo.url;
} else {
return data.bonus.link;
}
},
},
},
};
};
Where {binary_slug} is the URL path segment for binary options (e.g., binarni-opce for CS, binary-options for EN).
For games/ and providers/, follow the same closure pattern with tags: ["games"] / tags: ["providers"] and appropriate layouts and slugs.
These filter/listing pages are not managed by .11tydata.js closure patterns — they use explicit permalink in front matter:
| File | Permalink | Layout |
|---|---|---|
forex/forex.njk | /{url_prefix}/forex/ | layouts/forex.njk |
binaries/binaries.njk | /{url_prefix}/{binary_list_slug}/ | layouts/binaries.njk |
casinos/crypto.njk | /{url_prefix}/{casino_list_slug}/{crypto_slug}/ | layouts/base.njk |
IMPORTANT — affiliate-programs.njk caveat: This page uses a hardcoded {% set programs = [...] %} array with program data, not a collection loop. When creating it for a new language, copy from an existing language (e.g., ES) and translate descriptions, FAQ, and labels. Program names, commissions, links, and logos stay unchanged across languages. Do NOT use collections.affiliatePrograms — it will render empty.
Create content/{lang}/casinos/1xbet.njk. Use the Spanish version as source for structure, the English version as source for translation style.
Frontmatter rules:
| Field | Action |
|---|---|
date | Keep or update to current date |
slugOverride | Same as EN (1xbet) |
title | Translate to target language (keep brand name "1xBet") |
description | Translate to target language |
excerpt | Translate to target language |
operator | Keep unchanged (company name) |
gradient | Keep unchanged (brand colors) |
bonus.link | Use internal URL: /{url_prefix}/{casino_slug}/1xbet/ |
details.* | Keep unchanged (score values like "good", "average") |
maxWidthdrawal | Translate label; convert currency if needed |
minDeposit | Translate label; convert currency if needed |
minWidthdrawal | Translate label; convert currency if needed |
license | Keep unchanged (e.g., "Curaçao") |
reputation.code | Follow the content-guidelines skill for code usage |
reputation.text | Translate to target language |
featured, ranking, score | Keep unchanged |
Body rules:
h2 anchor IDs unchanged — they must match the Spanish IDs:
juegos, bonos, atencion-al-cliente, usabilidad_includes/components/casinos/casino-details.njk<h2 id="juegos" class="contentIndexTitle">Juegos ... → <h2 id="juegos" class="contentIndexTitle">{translated_games} ...languages.{lang}.texts values you set in Step 2:
"good" / "average" / "bad" from frontmatter details.* to the translated label"good" → Bom, "average" → Médio, "bad" → Ruimbadge-success, badge-warning, badge-error)/blog/que-es-el-rtp-en-las-tragamonedas/ → /{url_prefix}/{blog_slug}/{rtp_article_slug}//en/.../casinos/ → /{url_prefix}/{casino_list_slug}/npm run build
Checklist:
/{url_prefix}/index.html renders at _site/{url_prefix}/index.html/{url_prefix}/{casino_list_slug}/ renders at _site/{url_prefix}/{casino_list_slug}/index.html/{url_prefix}/{casino_slug}/1xbet/ renders at _site/{url_prefix}/{casino_slug}/1xbet/index.html/{url_prefix}/forex/ renders at _site/{url_prefix}/forex/index.html/{url_prefix}/{binary_list_slug}/ renders at _site/{url_prefix}/{binary_list_slug}/index.html/{url_prefix}/{casino_list_slug}/{crypto_slug}/ renders at _site/{url_prefix}/{casino_list_slug}/{crypto_slug}/index.html/{url_prefix}/affiliate-programme/ renders with hardcoded program list (not empty)locale_url nav links work (Casinos → /{url_prefix}/{casino_list_slug}/, Forex → /{url_prefix}/forex/)<head> include the new language alternate linkAfter the initial setup, add remaining content files in priority order:
pages/about.njk, pages/benefits.njk, pages/leaderboard.njk, etc.All non-default language .11tydata.js files use the closure pattern (module.exports = function () { var LANG = "{lang}"; return { ... }; }). Listing pages (casinos.njk, archive.njk, blacklisted.njk) use explicit permalink in front matter — they do NOT use slugOverride.
| Content type | .11tydata.js permalink pattern | Listing permalink |
|---|---|---|
Any page (via {lang}.11tydata.js) | "/" + LANG + "/" + this.slugify(data.slugOverride) + "/" | N/A |
Casino detail (via casinos.11tydata.js) | LANG + "/{casino_slug}/" + this.slugify(data.slugOverride) + "/" | /{lang}/{casino_list_slug}/ |
| Crypto filter (via explicit permalink) | (not used) | /{lang}/{casino_list_slug}/{crypto_slug}/ |
| Blacklist page (via explicit permalink) | (not used) | /{lang}/{casino_list_slug}/blacklist/ |
Page detail (via pages.11tydata.js) | LANG + "/" + this.slugify(data.slugOverride) + "/" | N/A |
Blog post (via blog.11tydata.js) | LANG + "/{blog_slug}/" + this.slugify(data.slugOverride) + "/" | /{lang}/{blog_list_slug}/ |
Forex detail (via forex.11tydata.js) | LANG + "/forex/" + this.slugify(data.slugOverride) + "/" | /{lang}/forex/ |
| Forex listing (via explicit permalink) | (not used) | /{lang}/forex/ |
Binary detail (via binaries.11tydata.js) | LANG + "/{binary_slug}/" + this.slugify(data.slugOverride) + "/" | /{lang}/{binary_list_slug}/ |
| Binary listing (via explicit permalink) | (not used) | /{lang}/{binary_list_slug}/ |
Game detail (via games.11tydata.js) | LANG + "/{game_slug}/" + this.slugify(data.slugOverride) + "/" | /{lang}/{games_list_slug}/ |
Provider detail (via providers.11tydata.js) | LANG + "/{provider_slug}/" + this.slugify(data.slugOverride) + "/" | /{lang}/{providers_list_slug}/ |
| Action | File | Notes |
|---|---|---|
| Create | _data/l10n/{lang}.js | All ~150 translated strings + menu, nav, promo, categories |
| Modify | _data/languages.js | Add {lang}: require("./l10n/{lang}.js") |
| Modify | _data/nav.js | Add {lang}: [ ... ] block with nav items |
| Modify | _data/categories.js | Add {lang}: buildGameCategoriesFromLanguage("{lang}") |
| Modify | _includes/components/bz-message.njk | Add elif for new flagClass (or refactor to flagSvg) |
| Modify | _includes/locale-fix.njk | Add elif for new flagClass (or refactor to flagSvg) |
| Modify | _includes/templates/languages-modal.template.njk | Add elif for new flagClass (or refactor to flagSvg) |
| Create | content/{lang}/{lang}.11tydata.js | Root language config (closure pattern) |
| Create | content/{lang}/index.njk | Home page |
| Create | content/{lang}/casinos/casinos.11tydata.js | Casino directory config (closure pattern) |
| Create | content/{lang}/casinos/casinos.njk | Casino listing page (explicit permalink) |
| Create | content/{lang}/casinos/blacklisted.njk | Blacklist page (layout: base.njk, explicit permalink) |
| Create | content/{lang}/casinos/crypto.njk | Crypto casino filter page (explicit permalink) |
| Create | content/{lang}/casinos/1xbet.njk | Starter casino review |
| Create | content/{lang}/pages/pages.11tydata.js | Static pages config (closure pattern) |
| Create | content/{lang}/pages/affiliate-programs.njk | Hardcoded {% set programs %} array — copy from ES, translate descriptions only |
| Create | content/{lang}/blog/blog.11tydata.js | Blog posts config (closure pattern) |
| Create | content/{lang}/blog/archive.njk | Blog listing page (explicit permalink) |
| Create | content/{lang}/forex/forex.11tydata.js | Forex brokers config (closure pattern) |
| Create | content/{lang}/forex/forex.njk | Forex listing page (explicit permalink, layout: layouts/forex.njk) |
| Create | content/{lang}/binaries/binaries.11tydata.js | Binary options config (closure pattern) |
| Create | content/{lang}/binaries/binaries.njk | Binary options listing page (explicit permalink, layout: layouts/binaries.njk) |