Skip to content

Commit fa4e379

Browse files
committed
feat: improve lang and theme switch
1 parent cb6a451 commit fa4e379

12 files changed

Lines changed: 104 additions & 47 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ on:
88
docs:
99
description: 'Deploy Docs'
1010
type: boolean
11-
default: true
11+
default: false
1212
blog:
1313
description: 'Deploy Blog'
1414
type: boolean
15-
default: true
15+
default: false
1616
website:
1717
description: 'Deploy Website'
1818
type: boolean
19-
default: true
19+
default: false
2020

2121
jobs:
2222
changes:
@@ -46,7 +46,7 @@ jobs:
4646
4747
build-docs:
4848
needs: changes
49-
if: needs.changes.outputs.docs == 'true' || (github.event_name == 'workflow_dispatch' && inputs.docs)
49+
if: (github.event_name == 'push' && needs.changes.outputs.docs == 'true') || (github.event_name == 'workflow_dispatch' && inputs.docs)
5050
runs-on: ubuntu-latest
5151
steps:
5252
- uses: actions/checkout@v5
@@ -69,7 +69,7 @@ jobs:
6969

7070
build-blog:
7171
needs: changes
72-
if: needs.changes.outputs.blog == 'true' || (github.event_name == 'workflow_dispatch' && inputs.blog) || github.event_name == 'schedule'
72+
if: (github.event_name == 'push' && needs.changes.outputs.blog == 'true') || (github.event_name == 'workflow_dispatch' && inputs.blog) || github.event_name == 'schedule'
7373
runs-on: ubuntu-latest
7474
steps:
7575
- uses: actions/checkout@v5
@@ -92,7 +92,7 @@ jobs:
9292

9393
build-website:
9494
needs: changes
95-
if: needs.changes.outputs.website == 'true' || (github.event_name == 'workflow_dispatch' && inputs.website)
95+
if: (github.event_name == 'push' && needs.changes.outputs.website == 'true') || (github.event_name == 'workflow_dispatch' && inputs.website)
9696
runs-on: ubuntu-latest
9797
steps:
9898
- uses: actions/checkout@v5
@@ -128,7 +128,7 @@ jobs:
128128
with:
129129
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
130130
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
131-
command: pages deploy dist --project-name=explainer-docs
131+
command: pages deploy dist --project-name=baptistep-docs
132132

133133
deploy-blog:
134134
needs: build-blog
@@ -146,7 +146,7 @@ jobs:
146146
with:
147147
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
148148
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
149-
command: pages deploy dist --project-name=explainer-blog
149+
command: pages deploy dist --project-name=baptistep-blog
150150

151151
deploy-website:
152152
needs: build-website
@@ -164,4 +164,4 @@ jobs:
164164
with:
165165
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
166166
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
167-
command: pages deploy dist --project-name=explainer-website
167+
command: pages deploy dist --project-name=baptistep-website

apps/blog/src/components/blog-navbar.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,20 @@ export function BlogNavbar({ activePath, appUrlOverrides, locale: initialLocale,
1616
const [locale, setLocale] = useState(initialLocale)
1717

1818
useEffect(() => {
19-
setLocale(initialLocale)
19+
if (!isListing) {
20+
setLocale(initialLocale)
21+
return
22+
}
23+
// On the listing page the server always renders with defaultLang.
24+
// Read the locale the inline script already detected from the cookie/browser.
25+
const detected = (window as any).__detectedLocale as string | undefined
26+
if (detected) setLocale(detected)
27+
28+
const handleLocaleChange = (e: Event) => {
29+
setLocale((e as CustomEvent<{ locale: string }>).detail.locale)
30+
}
31+
window.addEventListener('locale:change', handleLocaleChange)
32+
return () => window.removeEventListener('locale:change', handleLocaleChange)
2033
}, [initialLocale])
2134
const appLinks = getAppLinks('blog', appUrlOverrides)
2235
const t = useTranslations(locale)
@@ -32,6 +45,7 @@ export function BlogNavbar({ activePath, appUrlOverrides, locale: initialLocale,
3245
document.querySelectorAll<HTMLElement>('[data-locale]').forEach((el) => {
3346
el.style.display = el.dataset.locale === newLocale ? '' : 'none'
3447
})
48+
window.dispatchEvent(new CustomEvent('locale:change', { detail: { locale: newLocale } }))
3549
document.dispatchEvent(new Event('tags:filter'))
3650
}
3751
: undefined

apps/blog/src/layouts/base.astro

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,16 @@ const appUrlOverrides = {
4646
<ClientRouter />
4747
<script is:inline>
4848
(function () {
49-
var theme = localStorage.getItem('theme') || 'system'
49+
var match = document.cookie.match(/(?:^|; )theme=([^;]*)/)
50+
var theme = match ? match[1] : 'system'
5051
var isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
5152
document.documentElement.classList.toggle('dark', isDark)
5253
})()
5354
</script>
5455
<script is:inline>
5556
document.addEventListener('astro:after-swap', function () {
56-
var theme = localStorage.getItem('theme') || 'system'
57+
var match = document.cookie.match(/(?:^|; )theme=([^;]*)/)
58+
var theme = match ? match[1] : 'system'
5759
var isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
5860
document.documentElement.classList.toggle('dark', isDark)
5961
})

apps/blog/src/pages/index.astro

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { TagFilter } from '../components/TagFilter'
55
import PostCard from '../components/PostCard.astro'
66
import { getPublishedPosts, getPostHref, getPostLocale, getLocales, getAllTags, getReadingTime } from '../lib/posts'
77
import { getAuthor } from '../lib/authors'
8-
import { defaultLang } from '../i18n/ui'
8+
import { defaultLang, ui } from '../i18n/ui'
99
import { useTranslations } from '../i18n/utils'
1010
1111
const allPosts = await getCollection('posts')
@@ -20,7 +20,8 @@ const t = useTranslations(locale)
2020
<!-- Page header -->
2121
<section class="mb-8">
2222
<h1 class="text-4xl md:text-5xl font-bold tracking-tight">
23-
{t('index.heading.prefix')} <span class="text-primary">{t('index.heading.highlight')}</span>
23+
<span data-i18n="index.heading.prefix">{t('index.heading.prefix')}</span>{' '}
24+
<span class="text-primary" data-i18n="index.heading.highlight">{t('index.heading.highlight')}</span>
2425
</h1>
2526
</section>
2627

@@ -57,20 +58,32 @@ const t = useTranslations(locale)
5758

5859
</Base>
5960

60-
<script define:vars={{ supportedLocales: locales, fallbackLocale: locale }}>
61-
function filterByLocale() {
62-
const match = document.cookie.match(/(?:^|; )locale=([^;]*)/)
63-
const cookieLocale = match ? match[1] : null
64-
const browserLang = (navigator.language || '').split('-')[0]
65-
const detected = (cookieLocale && supportedLocales.includes(cookieLocale))
61+
<script define:vars={{ supportedLocales: locales, fallbackLocale: locale, translations: ui }}>
62+
function detectLocale() {
63+
var match = document.cookie.match(/(?:^|; )locale=([^;]*)/)
64+
var cookieLocale = match ? match[1] : null
65+
var browserLang = (navigator.language || '').split('-')[0]
66+
return (cookieLocale && supportedLocales.includes(cookieLocale))
6667
? cookieLocale
6768
: supportedLocales.includes(browserLang) ? browserLang : fallbackLocale
69+
}
6870

71+
function applyLocale(detected) {
72+
window.__detectedLocale = detected
6973
document.querySelectorAll('[data-locale]').forEach(function (el) {
7074
el.style.display = el.dataset.locale === detected ? '' : 'none'
7175
})
76+
window.dispatchEvent(new CustomEvent('locale:change', { detail: { locale: detected } }))
7277
}
7378

74-
filterByLocale()
75-
document.addEventListener('astro:after-swap', filterByLocale)
79+
window.addEventListener('locale:change', function (e) {
80+
var t = translations[e.detail.locale] || translations[fallbackLocale]
81+
document.querySelectorAll('[data-i18n]').forEach(function (el) {
82+
var key = el.dataset.i18n
83+
if (t[key] !== undefined) el.textContent = t[key]
84+
})
85+
})
86+
87+
applyLocale(detectLocale())
88+
document.addEventListener('astro:after-swap', function () { applyLocale(detectLocale()) })
7689
</script>

apps/docs/src/layouts/docs.astro

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ const appUrlOverrides = {
8686
<ClientRouter />
8787
<script is:inline>
8888
(() => {
89-
const theme = localStorage.getItem('theme') || 'system'
90-
const isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
89+
var match = document.cookie.match(/(?:^|; )theme=([^;]*)/)
90+
var theme = match ? match[1] : 'system'
91+
var isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
9192
document.documentElement.classList.toggle('dark', isDark)
9293
})()
9394
</script>
@@ -96,7 +97,8 @@ const appUrlOverrides = {
9697
if (!window.location.hash) {
9798
window.scrollTo({ top: 0 })
9899
}
99-
var theme = localStorage.getItem('theme') || 'system'
100+
var match = document.cookie.match(/(?:^|; )theme=([^;]*)/)
101+
var theme = match ? match[1] : 'system'
100102
var isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
101103
document.documentElement.classList.toggle('dark', isDark)
102104
})

apps/website/src/components/website-navbar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export function WebsiteNavbar({ appUrlOverrides }: WebsiteNavbarProps) {
4747
<LocaleSwitcher
4848
locales={['en', 'fr']}
4949
currentLocale={locale}
50-
onLocaleChange={() => window.location.reload()}
50+
onLocaleChange={(newLocale) => {
51+
setLocale(newLocale)
52+
window.dispatchEvent(new CustomEvent('locale:change', { detail: { locale: newLocale } }))
53+
}}
5154
/>
5255
}
5356
/>

apps/website/src/layouts/base.astro

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,16 @@ const thumbnailUrl = thumbnail ?? siteConfig.thumbnail
3333
<ClientRouter />
3434
<script is:inline>
3535
(function () {
36-
var theme = localStorage.getItem('theme') || 'system'
36+
var match = document.cookie.match(/(?:^|; )theme=([^;]*)/)
37+
var theme = match ? match[1] : 'system'
3738
var isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
3839
document.documentElement.classList.toggle('dark', isDark)
3940
})()
4041
</script>
4142
<script is:inline>
4243
document.addEventListener('astro:after-swap', function () {
43-
var theme = localStorage.getItem('theme') || 'system'
44+
var match = document.cookie.match(/(?:^|; )theme=([^;]*)/)
45+
var theme = match ? match[1] : 'system'
4446
var isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
4547
document.documentElement.classList.toggle('dark', isDark)
4648
})

apps/website/src/pages/index.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ const thumbnails = { en: '/thumbnail.png', fr: '/thumbnails/fr/thumbnail.png' }
4747
return 'en'
4848
}
4949

50-
function applyTranslations() {
51-
const locale = detectLocale()
50+
function applyTranslations(e) {
51+
const locale = (e instanceof CustomEvent && e.detail && e.detail.locale) ? e.detail.locale : detectLocale()
5252
document.documentElement.lang = locale
5353

5454
if (locale === 'en') {
@@ -72,4 +72,5 @@ const thumbnails = { en: '/thumbnail.png', fr: '/thumbnails/fr/thumbnail.png' }
7272

7373
applyTranslations()
7474
document.addEventListener('astro:after-swap', applyTranslations)
75+
window.addEventListener('locale:change', applyTranslations)
7576
</script>

packages/ui/src/components/locale-switcher.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import * as React from 'react'
44
import { Icon } from '@iconify/react'
55
import { cn } from '../lib/utils'
6+
import { setCookie } from '../lib/cookies'
67

78
export interface LocaleSwitcherProps {
89
locales: string[]
@@ -25,19 +26,6 @@ const localeNames: Record<string, string> = {
2526
it: 'Italiano',
2627
}
2728

28-
function setLocaleCookie(locale: string) {
29-
const hostname = window.location.hostname
30-
const parts = hostname.split('.')
31-
32-
// Set cookie without domain for public suffixes (e.g. pages.dev, github.io)
33-
// and with root domain for custom domains (e.g. .explainer.dev)
34-
const isPublicSuffix = parts.length <= 2 || ['pages.dev', 'github.io', 'vercel.app', 'netlify.app', 'workers.dev'].some(
35-
(s) => hostname.endsWith(s)
36-
)
37-
38-
const domainAttr = isPublicSuffix ? '' : `;domain=.${parts.slice(-2).join('.')}`
39-
document.cookie = `locale=${locale};path=/${domainAttr};max-age=${60 * 60 * 24 * 365};SameSite=Lax`
40-
}
4129

4230
export function LocaleSwitcher({ locales, currentLocale, switchUrls = {}, dropUp, onLocaleChange }: LocaleSwitcherProps) {
4331
const [open, setOpen] = React.useState(false)
@@ -67,7 +55,7 @@ export function LocaleSwitcher({ locales, currentLocale, switchUrls = {}, dropUp
6755
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
6856
)}
6957
onClick={(e) => {
70-
setLocaleCookie(locale)
58+
setCookie('locale', locale)
7159
setOpen(false)
7260
if (onLocaleChange) {
7361
e.preventDefault()

packages/ui/src/components/theme-toggle.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import * as React from 'react'
44
import { Icon } from '@iconify/react'
55
import { cn } from '../lib/utils'
6+
import { setCookie, getCookie } from '../lib/cookies'
67
import {
78
DropdownMenu,
89
DropdownMenuContent,
@@ -35,14 +36,14 @@ export function ThemeToggle({ className }: { className?: string }) {
3536
const [theme, setTheme] = React.useState<Theme>('light')
3637

3738
React.useEffect(() => {
38-
const stored = localStorage.getItem('theme') as Theme | null
39+
const stored = getCookie('theme') as Theme | null
3940
const initial: Theme = stored ?? 'system'
4041
setTheme(initial)
4142
applyTheme(initial)
4243

4344
const mq = window.matchMedia('(prefers-color-scheme: dark)')
4445
const onChange = () => {
45-
if ((localStorage.getItem('theme') ?? 'system') === 'system') {
46+
if ((getCookie('theme') ?? 'system') === 'system') {
4647
applyTheme('system')
4748
}
4849
}
@@ -52,7 +53,7 @@ export function ThemeToggle({ className }: { className?: string }) {
5253

5354
const select = (next: Theme) => {
5455
setTheme(next)
55-
localStorage.setItem('theme', next)
56+
setCookie('theme', next)
5657
applyTheme(next)
5758
}
5859

0 commit comments

Comments
 (0)