Skip to content

Commit 841f6b1

Browse files
heiskrCopilot
andauthored
Refactor languages module: separate client and server code (#57949)
Co-authored-by: Copilot <[email protected]>
1 parent f4ea74a commit 841f6b1

File tree

63 files changed

+230
-297
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+230
-297
lines changed

next.config.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,9 @@ import type { NextConfig } from 'next'
44

55
import frontmatter from '@gr2m/gray-matter'
66
import { getLogLevelNumber } from '@/observability/logger/lib/log-levels'
7+
import { languageKeys } from '@/languages/lib/languages'
78

89
const ROOT = process.env.ROOT || '.'
9-
10-
// Language keys are defined here because Next.js config compilation doesn't resolve the @/ path alias
11-
// Importing from src/languages/lib/languages.ts would fail when it tries to import @/frame/lib/constants
12-
// This must match the languages defined in src/languages/lib/languages.ts
13-
const languageKeys = ['en', 'es', 'ja', 'pt', 'zh', 'ru', 'fr', 'ko', 'de']
14-
1510
const homepage = path.posix.join(ROOT, 'content/index.md')
1611
const { data } = frontmatter(fs.readFileSync(homepage, 'utf8'))
1712
const productIds = data.children as string[]

src/app/client-layout.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { initializeEvents } from '@/events/components/events'
1010
import { CTAPopoverProvider } from '@/frame/components/context/CTAContext'
1111
import { SharedUIContextProvider } from '@/frame/components/context/SharedUIContext'
1212
import { LanguagesContext, LanguagesContextT } from '@/languages/components/LanguagesContext'
13-
import { clientLanguages, type ClientLanguageCode } from '@/languages/lib/client-languages'
13+
import { languages, type LanguageCode } from '@/languages/lib/languages'
1414
import { MainContextProvider } from '@/app/components/MainContextProvider'
1515
import { createMinimalMainContext } from '@/app/lib/main-context-adapter'
1616
import type { AppRouterContext } from '@/app/lib/app-router-context'
@@ -31,16 +31,11 @@ interface ClientLayoutProps {
3131

3232
export function ClientLayout({ children, appContext, pageData }: ClientLayoutProps): JSX.Element {
3333
const { theme } = useTheme()
34-
const locale: ClientLanguageCode = useDetectLocale()
34+
const locale: LanguageCode = useDetectLocale()
3535
const [isInitialized, setIsInitialized] = useState(false)
3636
const [initializationError, setInitializationError] = useState<Error | null>(null)
3737

38-
const languagesContext: LanguagesContextT = useMemo(
39-
() => ({
40-
languages: clientLanguages,
41-
}),
42-
[],
43-
)
38+
const languagesContext: LanguagesContextT = useMemo(() => ({ languages }), [])
4439

4540
// Create MainContext-compatible data for App Router
4641
const mainContext = useMemo(

src/app/components/AppRouterLanguagesContext.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { createContext, useContext } from 'react'
4-
import { clientLanguages, type ClientLanguageCode } from '@/languages/lib/client-languages'
4+
import { languages, type LanguageCode } from '@/languages/lib/languages'
55

66
export type AppRouterLanguageItem = {
77
name: string
@@ -12,7 +12,7 @@ export type AppRouterLanguageItem = {
1212

1313
export type AppRouterLanguagesContextT = {
1414
languages: Record<string, AppRouterLanguageItem>
15-
currentLanguage?: ClientLanguageCode
15+
currentLanguage?: LanguageCode
1616
}
1717

1818
export const AppRouterLanguagesContext = createContext<AppRouterLanguagesContextT | null>(null)
@@ -34,15 +34,15 @@ export const useAppRouterLanguages = (): AppRouterLanguagesContextT => {
3434
*/
3535
interface AppRouterLanguagesProviderProps {
3636
children: React.ReactNode
37-
currentLanguage?: ClientLanguageCode
37+
currentLanguage?: LanguageCode
3838
}
3939

4040
export function AppRouterLanguagesProvider({
4141
children,
4242
currentLanguage,
4343
}: AppRouterLanguagesProviderProps) {
4444
const value: AppRouterLanguagesContextT = {
45-
languages: clientLanguages,
45+
languages,
4646
currentLanguage,
4747
}
4848

src/app/components/ServerFooter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { getUIDataMerged } from '@/data-directory/lib/get-data'
22
import { createTranslationFunctions } from '@/languages/lib/translation-utils'
33
import { LinkExternalIcon } from '@primer/octicons-react'
4-
import type { ClientLanguageCode } from '@/languages/lib/client-languages'
4+
import type { LanguageCode } from '@/languages/lib/languages'
55

66
interface ServerFooterProps {
7-
currentLanguage: ClientLanguageCode
7+
currentLanguage: LanguageCode
88
}
99

1010
export function ServerFooter({ currentLanguage }: ServerFooterProps) {

src/app/lib/app-router-context.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { getUIDataMerged } from '@/data-directory/lib/get-data'
2-
import { type ClientLanguageCode } from '@/languages/lib/client-languages'
2+
import { type LanguageCode } from '@/languages/lib/languages'
33
import { translate } from '@/languages/lib/translation-utils'
44
import { extractLanguageFromPath } from '@/app/lib/language-utils'
55

66
export interface AppRouterContext {
7-
currentLanguage: ClientLanguageCode
7+
currentLanguage: LanguageCode
88
currentVersion: string
99
sitename: string
1010
site: {
@@ -19,7 +19,7 @@ export interface AppRouterContext {
1919
*/
2020
export function createAppRouterContext(
2121
pathname: string = '/',
22-
fallbackLanguage?: ClientLanguageCode,
22+
fallbackLanguage?: LanguageCode,
2323
): AppRouterContext {
2424
let language = extractLanguageFromPath(pathname)
2525

src/app/lib/language-utils.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { clientLanguageKeys, type ClientLanguageCode } from '@/languages/lib/client-languages'
1+
import { languageKeys, type LanguageCode } from '@/languages/lib/languages'
22

33
/**
44
* Extract language from URL path
55
* Handles paths like /en/something, /es/articles, etc.
66
*/
7-
export function extractLanguageFromPath(path: string): ClientLanguageCode {
7+
export function extractLanguageFromPath(path: string): LanguageCode {
88
try {
99
const pathSegments = path.split('/')
1010
const firstSegment = pathSegments[1]
1111

12-
if (firstSegment && clientLanguageKeys.includes(firstSegment)) {
13-
return firstSegment as ClientLanguageCode
12+
if (firstSegment && languageKeys.includes(firstSegment)) {
13+
return firstSegment as LanguageCode
1414
}
1515
} catch (error) {
1616
console.warn('Failed to extract language from path:', error)
@@ -24,7 +24,7 @@ export function extractLanguageFromPath(path: string): ClientLanguageCode {
2424
export function hasLanguagePrefix(path: string): boolean {
2525
const pathSegments = path.split('/')
2626
const firstSegment = pathSegments[1]
27-
return Boolean(firstSegment && clientLanguageKeys.includes(firstSegment))
27+
return Boolean(firstSegment && languageKeys.includes(firstSegment))
2828
}
2929

3030
/**
@@ -43,7 +43,7 @@ export function stripLanguagePrefix(path: string): string {
4343
* Add language prefix to path if it doesn't have one
4444
* e.g., /articles/example + 'es' -> /es/articles/example
4545
*/
46-
export function addLanguagePrefix(path: string, language: ClientLanguageCode): string {
46+
export function addLanguagePrefix(path: string, language: LanguageCode): string {
4747
if (hasLanguagePrefix(path)) {
4848
return path
4949
}

src/app/lib/locale-context.tsx

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,51 @@
11
'use client'
22

33
import { createContext, useContext, ReactNode, useMemo } from 'react'
4-
import {
5-
clientLanguages,
6-
clientLanguageKeys,
7-
type ClientLanguageCode,
8-
} from '@/languages/lib/client-languages'
4+
import { languages, languageKeys, type LanguageCode } from '@/languages/lib/languages'
95

106
interface LocaleContextType {
11-
readonly locale: ClientLanguageCode
12-
readonly isValidLocale: (locale: string) => locale is ClientLanguageCode
13-
readonly getSupportedLocales: () => readonly ClientLanguageCode[]
14-
readonly getLocaleDisplayName: (locale: ClientLanguageCode) => string
15-
readonly getLocaleNativeName: (locale: ClientLanguageCode) => string
7+
readonly locale: LanguageCode
8+
readonly isValidLocale: (locale: string) => locale is LanguageCode
9+
readonly getSupportedLocales: () => readonly LanguageCode[]
10+
readonly getLocaleDisplayName: (locale: LanguageCode) => string
11+
readonly getLocaleNativeName: (locale: LanguageCode) => string
1612
}
1713

1814
const LocaleContext = createContext<LocaleContextType | null>(null)
1915

2016
interface LocaleProviderProps {
2117
readonly children: ReactNode
22-
readonly locale: ClientLanguageCode
18+
readonly locale: LanguageCode
2319
}
2420

2521
// Use client languages as the source of truth for supported locales
26-
const SUPPORTED_LOCALES: readonly ClientLanguageCode[] = clientLanguageKeys as ClientLanguageCode[]
22+
const SUPPORTED_LOCALES: readonly LanguageCode[] = languageKeys as LanguageCode[]
2723

2824
/**
2925
* Validates if a string is a supported locale
3026
*/
31-
function isValidLocale(locale: string): locale is ClientLanguageCode {
32-
return clientLanguageKeys.includes(locale)
27+
function isValidLocale(locale: string): locale is LanguageCode {
28+
return languageKeys.includes(locale)
3329
}
3430

3531
/**
36-
* Gets display name for a locale from client languages data
32+
* Gets display name for a locale from languages module
3733
*/
38-
function getLocaleDisplayName(locale: ClientLanguageCode): string {
39-
return clientLanguages[locale]?.name || locale
34+
function getLocaleDisplayName(locale: LanguageCode): string {
35+
return languages[locale]?.name || locale
4036
}
4137

4238
/**
43-
* Gets native name for a locale from client languages data
39+
* Gets native name for a locale from languages module
4440
*/
45-
function getLocaleNativeName(locale: ClientLanguageCode): string {
46-
return clientLanguages[locale]?.nativeName || clientLanguages[locale]?.name || locale
41+
function getLocaleNativeName(locale: LanguageCode): string {
42+
return languages[locale]?.nativeName || languages[locale]?.name || locale
4743
}
4844

4945
/**
5046
* Gets browser language preference as a valid locale
5147
*/
52-
function getBrowserLocale(): ClientLanguageCode {
48+
function getBrowserLocale(): LanguageCode {
5349
if (typeof window === 'undefined') return 'en'
5450

5551
const browserLang = window.navigator.language.split('-')[0]
@@ -77,7 +73,7 @@ export function LocaleProvider({ children, locale }: LocaleProviderProps): JSX.E
7773
/**
7874
* Hook to get current locale with enhanced error handling
7975
*/
80-
export function useLocale(): ClientLanguageCode {
76+
export function useLocale(): LanguageCode {
8177
const context = useContext(LocaleContext)
8278

8379
if (context) {
@@ -118,4 +114,4 @@ export function useLocaleContext(): LocaleContextType {
118114
}
119115

120116
export { isValidLocale, getLocaleDisplayName, getLocaleNativeName }
121-
export type { LocaleContextType, ClientLanguageCode }
117+
export type { LocaleContextType, LanguageCode }

src/app/lib/server-context-utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { extractLanguageFromPath } from '@/app/lib/language-utils'
22
import { extractVersionFromPath } from '@/app/lib/version-utils'
33
import { getUIDataMerged } from '@/data-directory/lib/get-data'
4-
import { type ClientLanguageCode } from '@/languages/lib/client-languages'
4+
import { type LanguageCode } from '@/languages/lib/languages'
55
import { createTranslationFunctions, translate } from '@/languages/lib/translation-utils'
66

77
export interface ServerAppRouterContext {
8-
currentLanguage: ClientLanguageCode
8+
currentLanguage: LanguageCode
99
currentVersion: string
1010
sitename: string
1111
site: { data: { ui: any } }
@@ -33,7 +33,7 @@ export function createServerAppRouterContext(pathname: string): ServerAppRouterC
3333
/**
3434
* Create server-side footer with translations
3535
*/
36-
export function createServerFooterContent(language: ClientLanguageCode) {
36+
export function createServerFooterContent(language: LanguageCode) {
3737
const uiData = getUIDataMerged(language)
3838
const { t } = createTranslationFunctions(uiData, 'footer')
3939

src/app/lib/use-detect-locale.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,24 @@
22

33
import { usePathname } from 'next/navigation'
44
import { useMemo, useEffect, useState } from 'react'
5-
import { clientLanguageKeys, type ClientLanguageCode } from '@/languages/lib/client-languages'
5+
import { languageKeys, type LanguageCode } from '@/languages/lib/languages'
66
import Cookies from '@/frame/components/lib/cookies'
77
import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants'
88

99
/**
1010
* Validates if a string is a supported locale using client languages
1111
*/
12-
function isValidLocale(locale: string): locale is ClientLanguageCode {
13-
return clientLanguageKeys.includes(locale)
12+
function isValidLocale(locale: string): locale is LanguageCode {
13+
return languageKeys.includes(locale)
1414
}
1515

1616
/**
1717
* Hook to detect locale from various sources with fallback logic
1818
*/
19-
export function useDetectLocale(): ClientLanguageCode {
19+
export function useDetectLocale(): LanguageCode {
2020
const pathname = usePathname()
21-
const [cookieLanguage, setCookieLanguage] = useState<ClientLanguageCode | null>(null)
22-
const [browserLanguage, setBrowserLanguage] = useState<ClientLanguageCode | null>(null)
21+
const [cookieLanguage, setCookieLanguage] = useState<LanguageCode | null>(null)
22+
const [browserLanguage, setBrowserLanguage] = useState<LanguageCode | null>(null)
2323

2424
// Read cookie and browser language on client-side mount
2525
useEffect(() => {
@@ -71,7 +71,7 @@ export function useDetectLocale(): ClientLanguageCode {
7171
/**
7272
* Utility function to detect locale from pathname (for server-side use)
7373
*/
74-
export function detectLocaleFromPath(pathname: string): ClientLanguageCode {
74+
export function detectLocaleFromPath(pathname: string): LanguageCode {
7575
const pathSegments = pathname.split('/')
7676
const firstSegment = pathSegments[1]
7777

@@ -82,8 +82,8 @@ export function detectLocaleFromPath(pathname: string): ClientLanguageCode {
8282
return 'en'
8383
}
8484

85-
export function getSupportedLocales(): readonly ClientLanguageCode[] {
86-
return clientLanguageKeys as ClientLanguageCode[]
85+
export function getSupportedLocales(): readonly LanguageCode[] {
86+
return languageKeys as LanguageCode[]
8787
}
8888

8989
export { isValidLocale }

src/app/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
* Enhanced type definitions for the app router with strict validation
33
*/
44

5-
import type { ClientLanguageCode } from '@/languages/lib/client-languages'
5+
import type { LanguageCode } from '@/languages/lib/languages'
66

77
// Core theme types with strict validation
88
export type Theme = 'light' | 'dark' | 'auto'
99
export type ColorMode = 'light' | 'dark'
1010

11-
// Re-export locale type from client-languages for consistency
12-
export type Locale = ClientLanguageCode
11+
// Re-export locale type from languages.ts for consistency
12+
export type Locale = LanguageCode
1313

1414
// Version and product identifiers with validation
1515
export type VersionId = string

0 commit comments

Comments
 (0)