Skip to content

Add sitewide header language selector with route-preserving locale switching#236

Open
Copilot wants to merge 6 commits into
mainfrom
copilot/create-implementation-plan-for-translation
Open

Add sitewide header language selector with route-preserving locale switching#236
Copilot wants to merge 6 commits into
mainfrom
copilot/create-implementation-plan-for-translation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 22, 2026

This PR adds a sitewide language selector to the top navigation so users can switch locale from any page. Locale changes now keep users on the equivalent route instead of sending them to locale roots.

  • Header navigation: language selector

    • Adds a locale dropdown in header.jsx powered by SUPPORTED_LOCALES / LOCALE_NAMES.
    • Uses current locale from LocaleContext.
    • On selection change, navigates to a locale-adjusted path rather than a fixed landing page.
  • Locale routing: path rewrite helpers

    • Adds getLocaleSwitchPath(pathname, locale) in i18n/index.js for bidirectional locale switching across:
      • non-exercise routes (/about/fr-CA/about)
      • exercise routes (/exercises/en-US/python/E1//exercises/fr-CA/python/E1/)
      • exercise root behavior (/exercises//exercises/<locale>/)
    • Refines getLocalePath to preserve exercise subpaths when localizing paths.
  • Localization strings + UI styling

    • Adds nav.language label translations in strings.js for en-US, fr-CA, and pt-BR.
    • Adds header/mobile styles for selector layout and spacing in style.css and mobile.css.
  • Coverage updates

    • Extends i18n/index.spec.js with route-switching expectations for exercise and non-exercise paths, including default-locale edge behavior.
export function getLocaleSwitchPath(pathname, locale) {
  // preserves route structure while rewriting locale segment
  // e.g. /exercises/en-US/python/E1/ -> /exercises/fr-CA/python/E1/
}

Copilot AI changed the title feat: full site i18n for en-US, fr-CA, pt-BR Fix exercise template SSR locale resolution to unblock Gatsby static builds May 22, 2026
Copilot AI requested a review from mimiflynn May 22, 2026 19:43
Copilot AI changed the title Fix exercise template SSR locale resolution to unblock Gatsby static builds Add sitewide header language selector with route-preserving locale switching May 22, 2026
@mimiflynn mimiflynn marked this pull request as ready for review May 22, 2026 20:33
@mimiflynn mimiflynn requested review from Copilot and rmoriarty May 22, 2026 20:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a locale-aware i18n layer and exposes it via a sitewide header language selector that preserves the current route when switching languages.

Changes:

  • Introduces site/src/i18n helpers (getLocaleFromPath, getLocalePath, getLocaleSwitchPath) plus a LocaleContext provider/hook to supply locale + translated UI strings.
  • Updates header/nav and multiple components to use localized labels (exercise UI strings, footer message, summary card terms).
  • Adds locale-specific content pages (fr-CA, pt-BR), plus styling updates for the new selector and related layout.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
site/src/utils/language.js Defaults getCurrentLanguage locales from i18n constants.
site/src/templates/page.jsx Passes heroId from frontmatter into Hero.
site/src/templates/index.jsx Passes heroId from frontmatter into Hero.
site/src/templates/exercise.jsx Uses i18n strings for “Exercise” navigation labels.
site/src/styles/style.css Adds header layout styles for nav + language selector.
site/src/styles/mobile.css Adjusts header controls layout on mobile.
site/src/styles/global.css Reformatting + global style adjustments (also touches link selectors).
site/src/i18n/strings.spec.js Adds tests to validate per-locale string key consistency + fallback behavior.
site/src/i18n/strings.js Adds translated UI strings for nav/footer/exercise/summary cards.
site/src/i18n/LocaleContext.jsx Adds LocaleProvider/useLocale to expose locale + strings.
site/src/i18n/index.spec.js Adds tests for locale detection/path rewriting helpers.
site/src/i18n/index.js Implements locale constants, string lookup, and route rewrite helpers.
site/src/components/translations.jsx Displays human-friendly locale names in translations list.
site/src/components/summary-card.jsx Localizes SummaryCard term labels via LocaleContext.
site/src/components/site-map.jsx Localizes “Level” headings via LocaleContext.
site/src/components/layout.jsx Wraps app layout in LocaleProvider.
site/src/components/hero.jsx Supports stable heroId-based hero class naming.
site/src/components/header.jsx Adds locale dropdown and localized nav labels with route-preserving switching.
site/src/components/footer.jsx Localizes footer text via LocaleContext.
site/src/components/exercises.jsx Localizes exercise list labels via LocaleContext.
site/src/components/exercise-nav.jsx Localizes “Level” headings via LocaleContext.
site/content/pt-BR/teach.mdx Adds pt-BR localized Teach page content + heroId.
site/content/pt-BR/makerspace.mdx Adds pt-BR localized Makerspace page content + heroId.
site/content/pt-BR/continue.mdx Adds pt-BR localized Continue page content.
site/content/pt-BR/about.mdx Adds pt-BR localized About page content + heroId.
site/content/fr-CA/teach.mdx Adds fr-CA localized Teach page content + heroId.
site/content/fr-CA/makerspace.mdx Adds fr-CA localized Makerspace page content + heroId.
site/content/fr-CA/continue.mdx Adds fr-CA localized Continue page content.
site/content/fr-CA/about.mdx Adds fr-CA localized About page content + heroId.
site/content/exercises/pt-BR/index.mdx Adds heroId for pt-BR Learn index page.
site/content/exercises/fr-CA/index.mdx Adds heroId for fr-CA Learn index page.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 55 to 57
a:visted {
color: var(--accent);
}
Comment on lines +53 to +61
<label className="language-selector">
<select value={locale} onChange={onLocaleChange}>
{SUPPORTED_LOCALES.map((supportedLocale) => (
<option key={supportedLocale} value={supportedLocale}>
{LOCALE_NAMES[supportedLocale]}
</option>
))}
</select>
</label>
Comment on lines +16 to 26
const links = [
{ label: nav.learn, path: getLocalePath('/exercises', locale) },
{ label: nav.teach, path: getLocalePath('/teach', locale) },
{ label: nav.makerspace, path: getLocalePath('/makerspace', locale) },
{ label: nav.about, path: getLocalePath('/about', locale) },
];

function menuLink({ label, path }) {
const classname =
path === location.pathname ? 'nav-link-current' : 'nav-link';

Comment thread site/src/i18n/strings.js
Comment on lines +10 to +14
footer: {
message:
'Thanks! We hope you found what you are looking for. Please feel free to contribute via',
github: 'Github',
},
Comment thread site/src/i18n/strings.js
Comment on lines +38 to +42
footer: {
message:
"Merci\u00a0! Nous espérons que vous avez trouvé ce que vous cherchiez. N'hésitez pas à contribuer via",
github: 'Github',
},
Comment thread site/src/i18n/strings.js
Comment on lines +66 to +70
footer: {
message:
'Obrigado! Esperamos que você tenha encontrado o que procurava. Sinta-se à vontade para contribuir via',
github: 'Github',
},
Comment on lines +4 to +16
const topLevelKeys = Object.keys(strings[DEFAULT_LOCALE]);

test('all locales define the same top-level sections', () => {
SUPPORTED_LOCALES.forEach((locale) => {
expect(Object.keys(strings[locale])).toEqual(topLevelKeys);
});
});

test('all locales define the same keys within each section', () => {
SUPPORTED_LOCALES.forEach((locale) => {
topLevelKeys.forEach((section) => {
expect(Object.keys(strings[locale][section])).toEqual(
Object.keys(strings[DEFAULT_LOCALE][section])
Comment on lines +4 to +16
const topLevelKeys = Object.keys(strings[DEFAULT_LOCALE]);

test('all locales define the same top-level sections', () => {
SUPPORTED_LOCALES.forEach((locale) => {
expect(Object.keys(strings[locale])).toEqual(topLevelKeys);
});
});

test('all locales define the same keys within each section', () => {
SUPPORTED_LOCALES.forEach((locale) => {
topLevelKeys.forEach((section) => {
expect(Object.keys(strings[locale][section])).toEqual(
Object.keys(strings[DEFAULT_LOCALE][section])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants