agentsclimarketplace

Gh translate button

Skill aymandakirgh/ghclaudetoolkit/skills/gh-translate-button

Use when the user wants to add a language switcher, a translate button, an i18n toggle, or multi-language support to a page or component. Triggers on "translation button", "language switcher", "language selector", "i18n toggle", "add languages to this page", "multi-language", "localize this", "RTL support", "Arabic support", "lang switcher", "translate widget". Specs a reusable component supporting EN, IT, AR, NL, ZH-CN, ES, FR, DE, PT-BR — with Arabic as RTL and the active language driven by a ?lang= URL param (never localStorage) — and ships a drop-in vanilla HTML/JS snippet.From its SKILL.md

Install
npx -y skills add aymandakirgh/ghclaudetoolkit --skill gh-translate-button

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

6.2 KB, as published. Nobody here has run it

gh-translate-button

A reusable, dependency-free language switcher. Drop it into any page; it switches UI strings, flips direction for RTL, and persists the choice only through the ?lang= URL parameter.

Requirements (non-negotiable)

  • Languages (9): en, it, ar, nl, zh-CN, es, fr, de, pt-BR.
  • Arabic is RTL. When ar is active, set dir="rtl" on <html>; all others are ltr.
  • State lives in ?lang= only. The active language is read from and written to the URL query param. Never use localStorage/cookies — the URL is the single source of truth, so a shared link reproduces the exact language, and there's no hidden persisted state.
  • No reload on switch. Update the URL with history.replaceState and re-render in place.
  • Graceful fallback. Unknown/missing ?lang= → default en. Missing key in a dictionary → fall back to the en string.
  • Accessible. The control is a labelled <select> (or button group) with an aria-label.

How content is marked up

Tag any translatable element with data-i18n="<key>"; the script swaps its textContent per the active language's dictionary. Set <html lang> and dir automatically.

Drop-in snippet (vanilla, no build step)

<!-- 1) Place the switcher anywhere -->
<div id="lang-switcher" aria-label="Language"></div>

<!-- 2) Mark translatable text -->
<h1 data-i18n="hero_title">We handle it</h1>
<a href="#" data-i18n="cta">Get started</a>

<!-- 3) Drop-in logic -->
<script>
(function () {
  // Supported languages. `rtl: true` only for Arabic.
  const LANGS = [
    { code: 'en',    label: 'English',    rtl: false },
    { code: 'it',    label: 'Italiano',   rtl: false },
    { code: 'ar',    label: 'العربية',     rtl: true  },
    { code: 'nl',    label: 'Nederlands', rtl: false },
    { code: 'zh-CN', label: '简体中文',     rtl: false },
    { code: 'es',    label: 'Español',    rtl: false },
    { code: 'fr',    label: 'Français',   rtl: false },
    { code: 'de',    label: 'Deutsch',    rtl: false },
    { code: 'pt-BR', label: 'Português',  rtl: false },
  ];
  const DEFAULT = 'en';

  // String table: I18N[code][key]. Fill in your real copy; en is the fallback.
  const I18N = {
    'en':    { hero_title: 'We handle it',     cta: 'Get started' },
    'it':    { hero_title: 'Lo gestiamo noi',  cta: 'Inizia' },
    'ar':    { hero_title: 'نحن نتولى الأمر',   cta: 'ابدأ الآن' },
    'nl':    { hero_title: 'Wij regelen het',  cta: 'Begin nu' },
    'zh-CN': { hero_title: '交给我们',          cta: '开始使用' },
    'es':    { hero_title: 'Nos encargamos',   cta: 'Empezar' },
    'fr':    { hero_title: 'On s’en occupe',   cta: 'Commencer' },
    'de':    { hero_title: 'Wir kümmern uns',  cta: 'Loslegen' },
    'pt-BR': { hero_title: 'A gente cuida',    cta: 'Começar' },
  };

  const known = c => LANGS.some(l => l.code === c);

  // Active language = ?lang= param, else default. No storage, ever.
  function currentLang() {
    const p = new URLSearchParams(location.search).get('lang');
    return known(p) ? p : DEFAULT;
  }

  function apply(code) {
    const lang = LANGS.find(l => l.code === code) || LANGS.find(l => l.code === DEFAULT);
    const dict = I18N[lang.code] || {};
    const fallback = I18N[DEFAULT] || {};
    document.documentElement.lang = lang.code;
    document.documentElement.dir = lang.rtl ? 'rtl' : 'ltr';
    document.querySelectorAll('[data-i18n]').forEach(el => {
      const key = el.getAttribute('data-i18n');
      const val = dict[key] != null ? dict[key] : fallback[key];
      if (val != null) el.textContent = val;
    });
  }

  function setLang(code) {
    const url = new URL(location.href);
    url.searchParams.set('lang', code);   // the ONLY place state is kept
    history.replaceState(null, '', url);  // no reload, no localStorage
    apply(code);
    render(code);
  }

  function render(active) {
    const host = document.getElementById('lang-switcher');
    if (!host) return;
    host.innerHTML = '';
    const sel = document.createElement('select');
    sel.setAttribute('aria-label', 'Select language');
    LANGS.forEach(l => {
      const opt = document.createElement('option');
      opt.value = l.code;
      opt.textContent = l.label;
      if (l.code === active) opt.selected = true;
      sel.appendChild(opt);
    });
    sel.addEventListener('change', e => setLang(e.target.value));
    host.appendChild(sel);
  }

  function init() { const c = currentLang(); apply(c); render(c); }
  window.addEventListener('popstate', init);        // back/forward keeps URL ↔ UI in sync
  if (document.readyState !== 'loading') init();
  else document.addEventListener('DOMContentLoaded', init);
})();
</script>

Adapting it

  • Add a string: add the key to every I18N[code]; mark the element with data-i18n="key".
  • Add a language: append to LANGS and add its I18N entry. Only ar is RTL today.
  • Framework port: the same three pieces (read ?lang=, write via history.replaceState, re-render) port directly to React/Vue — keep ?lang= as the source of truth; do not introduce localStorage.
  • SEO (optional): emit <link rel="alternate" hreflang="<code>" href="...?lang=<code>"> per language so each variant is discoverable.

Acceptance check

  • All 9 languages selectable; switching updates text without reload.
  • ?lang=ar sets dir="rtl"; others ltr.
  • State is in the URL only — no localStorage/cookies anywhere.
  • Unknown ?lang= and missing keys fall back to en.
  • Shared link with ?lang= reproduces the exact language.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.