Skip to content

Commit 71f805b

Browse files
Christian Shearerclaude
andcommitted
Add language picker dropdown with flags in header nav
Flag + language code button in top-right of header. Dropdown shows all 21 languages with emoji flags and native names. Removes old text-only language row from footer area. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4a5d326 commit 71f805b

File tree

2 files changed

+48
-5
lines changed

2 files changed

+48
-5
lines changed

src/server/routes.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import { getProjectForBatch, PROJECTS } from "./project-metadata.js";
5656
import { checkAndSendMonthlyReminder, checkTradableStock, sendTelegram } from "../services/admin-telegram.js";
5757
import { updateRegistryProfile } from "../services/registry-profile.js";
5858
import { brandFonts, brandCSS, brandHeader, brandFooter } from "./brand.js";
59-
import { t, SUPPORTED_LANGS, LANG_NAMES, type LangCode } from "./translations.js";
59+
import { t, SUPPORTED_LANGS, LANG_NAMES, LANG_FLAGS, LANG_SHORT, type LangCode } from "./translations.js";
6060

6161
/** Per-subscriber lock to prevent concurrent retirement execution */
6262
const _subscriberLocks = new Map<number, Promise<void>>();
@@ -155,6 +155,18 @@ ${SUPPORTED_LANGS.map(l => ` <link rel="alternate" hreflang="${l}" href="${base
155155
${betaBannerCSS()}
156156
${brandCSS()}
157157
158+
/* Language picker */
159+
.lang-picker { position: relative; margin-left: 8px; }
160+
.lang-picker__btn { display: inline-flex; align-items: center; gap: 4px; background: none; border: 1px solid var(--regen-gray-200); border-radius: 6px; padding: 5px 10px; cursor: pointer; font-size: 13px; color: var(--regen-navy); font-family: inherit; transition: border-color 0.2s; }
161+
.lang-picker__btn:hover { border-color: var(--regen-green); }
162+
.lang-picker__flag { font-size: 16px; line-height: 1; }
163+
.lang-picker__code { font-weight: 600; font-size: 12px; }
164+
.lang-picker__menu { display: none; position: absolute; right: 0; top: calc(100% + 6px); background: var(--regen-white); border: 1px solid var(--regen-gray-200); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.12); padding: 6px 0; min-width: 200px; max-height: 360px; overflow-y: auto; z-index: 1000; }
165+
.lang-picker__menu.open { display: block; }
166+
.lang-picker__item { display: flex; align-items: center; gap: 8px; padding: 8px 16px; font-size: 14px; color: var(--regen-navy); text-decoration: none; transition: background 0.15s; }
167+
.lang-picker__item:hover { background: var(--regen-gray-50); }
168+
.lang-picker__item--active { font-weight: 700; color: var(--regen-green); }
169+
158170
/* How it works */
159171
.hiw-section { padding: 64px 0; border-top: 1px solid var(--regen-gray-200); }
160172
.hiw-steps {
@@ -393,7 +405,18 @@ ${SUPPORTED_LANGS.map(l => ` <link rel="alternate" hreflang="${l}" href="${base
393405
394406
${referralValid ? `<div class="regen-ref-banner"><span>${t(lang, "referral_banner_prefix")}</span> ${t(lang, "referral_banner_suffix")}</div>` : ""}
395407
396-
${brandHeader({ nav: [{ label: t(lang, "nav_ai_plugin"), href: "/ai-plugin" }, { label: t(lang, "nav_research"), href: "/research" }, { label: t(lang, "nav_about"), href: "/about" }, { label: t(lang, "nav_dashboard"), href: "/dashboard/login" }] })}
408+
${brandHeader({ nav: [{ label: t(lang, "nav_ai_plugin"), href: "/ai-plugin" }, { label: t(lang, "nav_research"), href: "/research" }, { label: t(lang, "nav_about"), href: "/about" }, { label: t(lang, "nav_dashboard"), href: "/dashboard/login" }] }).replace('</nav>', `
409+
<div class="lang-picker">
410+
<button class="lang-picker__btn" onclick="document.querySelector('.lang-picker__menu').classList.toggle('open')" type="button">
411+
<span class="lang-picker__flag">${LANG_FLAGS[lang]}</span>
412+
<span class="lang-picker__code">${LANG_SHORT[lang]}</span>
413+
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" style="margin-left:2px;"><path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
414+
</button>
415+
<div class="lang-picker__menu">
416+
${SUPPORTED_LANGS.map(l => `<a class="lang-picker__item${l === lang ? ' lang-picker__item--active' : ''}" href="${l === 'en' ? '/' : '/' + l}"><span class="lang-picker__flag">${LANG_FLAGS[l]}</span> ${LANG_NAMES[l]}</a>`).join('')}
417+
</div>
418+
</div>
419+
</nav>`)}
397420
398421
<!-- Hero -->
399422
<section class="regen-hero">
@@ -619,9 +642,6 @@ ${SUPPORTED_LANGS.map(l => ` <link rel="alternate" hreflang="${l}" href="${base
619642
</div>
620643
</section>
621644
622-
<div style="text-align:center;padding:16px 0 8px;">
623-
${SUPPORTED_LANGS.map(l => `<a href="${l === 'en' ? '/' : '/' + l}" style="display:inline-block;margin:4px 6px;font-size:13px;color:${l === lang ? 'var(--regen-green)' : 'var(--regen-gray-500)'};font-weight:${l === lang ? '700' : '400'};text-decoration:none;">${LANG_NAMES[l]}</a>`).join('')}
624-
</div>
625645
626646
${brandFooter({ showInstall: false, links: [
627647
{ label: "Regen Network", href: "https://regen.network" },
@@ -632,6 +652,15 @@ ${SUPPORTED_LANGS.map(l => ` <link rel="alternate" hreflang="${l}" href="${base
632652
<button onclick="window.location.href='/?view=agent'" style="position:fixed;bottom:24px;right:24px;z-index:9999;background:#1a1a2e;color:#4FB573;border:1px solid #4FB573;border-radius:8px;padding:10px 18px;cursor:pointer;font-family:monospace;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,0.3);transition:all 0.2s;" onmouseover="this.style.background='#2a2a4e'" onmouseout="this.style.background='#1a1a2e'">&#129302; Agent View</button>
633653
634654
<script>
655+
// Close language picker when clicking outside
656+
document.addEventListener('click', function(e) {
657+
var menu = document.querySelector('.lang-picker__menu');
658+
var btn = document.querySelector('.lang-picker__btn');
659+
if (menu && btn && !btn.contains(e.target) && !menu.contains(e.target)) {
660+
menu.classList.remove('open');
661+
}
662+
});
663+
635664
function showOrgForm() {
636665
document.getElementById('org-cta').style.display = 'none';
637666
document.getElementById('org-form').style.display = 'block';

src/server/translations.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ export const LANG_NAMES: Record<LangCode, string> = {
1212
it: "Italiano", nl: "Nederlands", pl: "Polski", ms: "Bahasa Melayu", sw: "Kiswahili", uk: "Українська",
1313
};
1414

15+
export const LANG_FLAGS: Record<LangCode, string> = {
16+
en: "\u{1F1EC}\u{1F1E7}", es: "\u{1F1EA}\u{1F1F8}", pt: "\u{1F1E7}\u{1F1F7}", fr: "\u{1F1EB}\u{1F1F7}", de: "\u{1F1E9}\u{1F1EA}",
17+
zh: "\u{1F1E8}\u{1F1F3}", ja: "\u{1F1EF}\u{1F1F5}", ko: "\u{1F1F0}\u{1F1F7}", hi: "\u{1F1EE}\u{1F1F3}", ar: "\u{1F1F8}\u{1F1E6}",
18+
ru: "\u{1F1F7}\u{1F1FA}", id: "\u{1F1EE}\u{1F1E9}", tr: "\u{1F1F9}\u{1F1F7}", vi: "\u{1F1FB}\u{1F1F3}", th: "\u{1F1F9}\u{1F1ED}",
19+
it: "\u{1F1EE}\u{1F1F9}", nl: "\u{1F1F3}\u{1F1F1}", pl: "\u{1F1F5}\u{1F1F1}", ms: "\u{1F1F2}\u{1F1FE}", sw: "\u{1F1F0}\u{1F1EA}", uk: "\u{1F1FA}\u{1F1E6}",
20+
};
21+
22+
export const LANG_SHORT: Record<LangCode, string> = {
23+
en: "EN", es: "ES", pt: "PT", fr: "FR", de: "DE",
24+
zh: "ZH", ja: "JA", ko: "KO", hi: "HI", ar: "AR",
25+
ru: "RU", id: "ID", tr: "TR", vi: "VI", th: "TH",
26+
it: "IT", nl: "NL", pl: "PL", ms: "MS", sw: "SW", uk: "UK",
27+
};
28+
1529
type TranslationStrings = { [key: string]: string };
1630

1731
export const translations: Record<LangCode, TranslationStrings> = {

0 commit comments

Comments
 (0)