From 841f6b171807d3f9f260a4b503bb8da51ffdc673 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 23 Oct 2025 12:30:06 -0700 Subject: [PATCH] Refactor languages module: separate client and server code (#57949) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- next.config.ts | 7 +- src/app/client-layout.tsx | 11 +- .../components/AppRouterLanguagesContext.tsx | 8 +- src/app/components/ServerFooter.tsx | 4 +- src/app/lib/app-router-context.ts | 6 +- src/app/lib/language-utils.ts | 12 +- src/app/lib/locale-context.tsx | 42 +++--- src/app/lib/server-context-utils.ts | 6 +- src/app/lib/use-detect-locale.tsx | 18 +-- src/app/types.ts | 6 +- .../archived-enterprise-versions.ts | 2 +- .../scripts/precompute-pageinfo.ts | 2 +- src/assets/scripts/find-orphaned-assets.ts | 2 +- .../scripts/convert-markdown-for-docs.ts | 2 +- .../linting-rules/internal-links-no-lang.ts | 4 +- src/content-linter/scripts/lint-content.ts | 2 +- src/content-linter/tests/lint-files.ts | 2 +- .../scripts/all-documents/cli.ts | 2 +- src/content-render/tests/data.ts | 2 +- src/content-render/tests/liquid-helpers.ts | 2 +- src/data-directory/lib/get-data.ts | 2 +- .../scripts/find-orphaned-features/delete.ts | 2 +- .../scripts/find-orphaned-features/find.ts | 2 +- src/data-directory/tests/get-data.ts | 2 +- src/early-access/tests/early-access-unit.ts | 2 +- src/events/lib/schema.ts | 2 +- src/frame/lib/page-data.ts | 2 +- src/frame/middleware/context/context.ts | 2 +- .../middleware/context/product-groups.ts | 2 +- src/frame/middleware/find-page.ts | 2 +- src/frame/middleware/helmet.ts | 2 +- src/frame/middleware/llms-txt.ts | 2 +- src/frame/middleware/reload-tree.ts | 2 +- src/frame/tests/pages.ts | 2 +- .../scripts/deprecate/archive-version.ts | 2 +- src/graphql/lib/index.ts | 2 +- src/languages/lib/client-languages.ts | 67 --------- src/languages/lib/get-alert-titles.ts | 2 +- src/languages/lib/languages-server.ts | 97 +++++++++++++ src/languages/lib/languages.ts | 130 ++++-------------- src/languages/middleware/detect-language.ts | 2 +- .../scripts/count-translation-corruptions.ts | 2 +- .../purge-fastly-edge-cache-per-language.ts | 2 +- src/languages/tests/files.ts | 2 +- src/languages/tests/frame.ts | 2 +- src/languages/tests/glossary.ts | 2 +- src/languages/tests/llms-txt-translations.ts | 2 +- src/languages/tests/redirects.ts | 2 +- src/languages/tests/search.ts | 2 +- src/products/lib/get-product-groups.ts | 2 +- src/redirects/lib/get-redirect.ts | 2 +- src/redirects/middleware/handle-redirects.ts | 2 +- .../middleware/language-code-redirects.ts | 2 +- src/rest/lib/index.ts | 2 +- src/search/lib/elasticsearch-indexes.ts | 2 +- .../search-params-objects.ts | 2 +- src/search/scripts/analyze-text.ts | 2 +- src/search/scripts/index/index-cli.ts | 2 +- .../scripts/index/lib/index-general-search.ts | 2 +- .../scripts/scrape/lib/build-records.ts | 2 +- .../scrape/lib/scrape-into-index-json.ts | 2 +- src/search/scripts/scrape/scrape-cli.ts | 2 +- src/types.ts | 13 +- 63 files changed, 230 insertions(+), 297 deletions(-) delete mode 100644 src/languages/lib/client-languages.ts create mode 100644 src/languages/lib/languages-server.ts diff --git a/next.config.ts b/next.config.ts index 5db11da8cb..ce4e8d3a92 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,14 +4,9 @@ import type { NextConfig } from 'next' import frontmatter from '@gr2m/gray-matter' import { getLogLevelNumber } from '@/observability/logger/lib/log-levels' +import { languageKeys } from '@/languages/lib/languages' const ROOT = process.env.ROOT || '.' - -// Language keys are defined here because Next.js config compilation doesn't resolve the @/ path alias -// Importing from src/languages/lib/languages.ts would fail when it tries to import @/frame/lib/constants -// This must match the languages defined in src/languages/lib/languages.ts -const languageKeys = ['en', 'es', 'ja', 'pt', 'zh', 'ru', 'fr', 'ko', 'de'] - const homepage = path.posix.join(ROOT, 'content/index.md') const { data } = frontmatter(fs.readFileSync(homepage, 'utf8')) const productIds = data.children as string[] diff --git a/src/app/client-layout.tsx b/src/app/client-layout.tsx index 60c80ec8c9..4cb24a6646 100644 --- a/src/app/client-layout.tsx +++ b/src/app/client-layout.tsx @@ -10,7 +10,7 @@ import { initializeEvents } from '@/events/components/events' import { CTAPopoverProvider } from '@/frame/components/context/CTAContext' import { SharedUIContextProvider } from '@/frame/components/context/SharedUIContext' import { LanguagesContext, LanguagesContextT } from '@/languages/components/LanguagesContext' -import { clientLanguages, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languages, type LanguageCode } from '@/languages/lib/languages' import { MainContextProvider } from '@/app/components/MainContextProvider' import { createMinimalMainContext } from '@/app/lib/main-context-adapter' import type { AppRouterContext } from '@/app/lib/app-router-context' @@ -31,16 +31,11 @@ interface ClientLayoutProps { export function ClientLayout({ children, appContext, pageData }: ClientLayoutProps): JSX.Element { const { theme } = useTheme() - const locale: ClientLanguageCode = useDetectLocale() + const locale: LanguageCode = useDetectLocale() const [isInitialized, setIsInitialized] = useState(false) const [initializationError, setInitializationError] = useState(null) - const languagesContext: LanguagesContextT = useMemo( - () => ({ - languages: clientLanguages, - }), - [], - ) + const languagesContext: LanguagesContextT = useMemo(() => ({ languages }), []) // Create MainContext-compatible data for App Router const mainContext = useMemo( diff --git a/src/app/components/AppRouterLanguagesContext.tsx b/src/app/components/AppRouterLanguagesContext.tsx index c33a612fe9..96aa54097d 100644 --- a/src/app/components/AppRouterLanguagesContext.tsx +++ b/src/app/components/AppRouterLanguagesContext.tsx @@ -1,7 +1,7 @@ 'use client' import { createContext, useContext } from 'react' -import { clientLanguages, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languages, type LanguageCode } from '@/languages/lib/languages' export type AppRouterLanguageItem = { name: string @@ -12,7 +12,7 @@ export type AppRouterLanguageItem = { export type AppRouterLanguagesContextT = { languages: Record - currentLanguage?: ClientLanguageCode + currentLanguage?: LanguageCode } export const AppRouterLanguagesContext = createContext(null) @@ -34,7 +34,7 @@ export const useAppRouterLanguages = (): AppRouterLanguagesContextT => { */ interface AppRouterLanguagesProviderProps { children: React.ReactNode - currentLanguage?: ClientLanguageCode + currentLanguage?: LanguageCode } export function AppRouterLanguagesProvider({ @@ -42,7 +42,7 @@ export function AppRouterLanguagesProvider({ currentLanguage, }: AppRouterLanguagesProviderProps) { const value: AppRouterLanguagesContextT = { - languages: clientLanguages, + languages, currentLanguage, } diff --git a/src/app/components/ServerFooter.tsx b/src/app/components/ServerFooter.tsx index d4b846381a..a6d5913a4b 100644 --- a/src/app/components/ServerFooter.tsx +++ b/src/app/components/ServerFooter.tsx @@ -1,10 +1,10 @@ import { getUIDataMerged } from '@/data-directory/lib/get-data' import { createTranslationFunctions } from '@/languages/lib/translation-utils' import { LinkExternalIcon } from '@primer/octicons-react' -import type { ClientLanguageCode } from '@/languages/lib/client-languages' +import type { LanguageCode } from '@/languages/lib/languages' interface ServerFooterProps { - currentLanguage: ClientLanguageCode + currentLanguage: LanguageCode } export function ServerFooter({ currentLanguage }: ServerFooterProps) { diff --git a/src/app/lib/app-router-context.ts b/src/app/lib/app-router-context.ts index 1997e009ab..3e0e8e75ec 100644 --- a/src/app/lib/app-router-context.ts +++ b/src/app/lib/app-router-context.ts @@ -1,10 +1,10 @@ import { getUIDataMerged } from '@/data-directory/lib/get-data' -import { type ClientLanguageCode } from '@/languages/lib/client-languages' +import { type LanguageCode } from '@/languages/lib/languages' import { translate } from '@/languages/lib/translation-utils' import { extractLanguageFromPath } from '@/app/lib/language-utils' export interface AppRouterContext { - currentLanguage: ClientLanguageCode + currentLanguage: LanguageCode currentVersion: string sitename: string site: { @@ -19,7 +19,7 @@ export interface AppRouterContext { */ export function createAppRouterContext( pathname: string = '/', - fallbackLanguage?: ClientLanguageCode, + fallbackLanguage?: LanguageCode, ): AppRouterContext { let language = extractLanguageFromPath(pathname) diff --git a/src/app/lib/language-utils.ts b/src/app/lib/language-utils.ts index 00f77fe685..173f4e9a1f 100644 --- a/src/app/lib/language-utils.ts +++ b/src/app/lib/language-utils.ts @@ -1,16 +1,16 @@ -import { clientLanguageKeys, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languageKeys, type LanguageCode } from '@/languages/lib/languages' /** * Extract language from URL path * Handles paths like /en/something, /es/articles, etc. */ -export function extractLanguageFromPath(path: string): ClientLanguageCode { +export function extractLanguageFromPath(path: string): LanguageCode { try { const pathSegments = path.split('/') const firstSegment = pathSegments[1] - if (firstSegment && clientLanguageKeys.includes(firstSegment)) { - return firstSegment as ClientLanguageCode + if (firstSegment && languageKeys.includes(firstSegment)) { + return firstSegment as LanguageCode } } catch (error) { console.warn('Failed to extract language from path:', error) @@ -24,7 +24,7 @@ export function extractLanguageFromPath(path: string): ClientLanguageCode { export function hasLanguagePrefix(path: string): boolean { const pathSegments = path.split('/') const firstSegment = pathSegments[1] - return Boolean(firstSegment && clientLanguageKeys.includes(firstSegment)) + return Boolean(firstSegment && languageKeys.includes(firstSegment)) } /** @@ -43,7 +43,7 @@ export function stripLanguagePrefix(path: string): string { * Add language prefix to path if it doesn't have one * e.g., /articles/example + 'es' -> /es/articles/example */ -export function addLanguagePrefix(path: string, language: ClientLanguageCode): string { +export function addLanguagePrefix(path: string, language: LanguageCode): string { if (hasLanguagePrefix(path)) { return path } diff --git a/src/app/lib/locale-context.tsx b/src/app/lib/locale-context.tsx index 7840c8af71..bad82ed432 100644 --- a/src/app/lib/locale-context.tsx +++ b/src/app/lib/locale-context.tsx @@ -1,55 +1,51 @@ 'use client' import { createContext, useContext, ReactNode, useMemo } from 'react' -import { - clientLanguages, - clientLanguageKeys, - type ClientLanguageCode, -} from '@/languages/lib/client-languages' +import { languages, languageKeys, type LanguageCode } from '@/languages/lib/languages' interface LocaleContextType { - readonly locale: ClientLanguageCode - readonly isValidLocale: (locale: string) => locale is ClientLanguageCode - readonly getSupportedLocales: () => readonly ClientLanguageCode[] - readonly getLocaleDisplayName: (locale: ClientLanguageCode) => string - readonly getLocaleNativeName: (locale: ClientLanguageCode) => string + readonly locale: LanguageCode + readonly isValidLocale: (locale: string) => locale is LanguageCode + readonly getSupportedLocales: () => readonly LanguageCode[] + readonly getLocaleDisplayName: (locale: LanguageCode) => string + readonly getLocaleNativeName: (locale: LanguageCode) => string } const LocaleContext = createContext(null) interface LocaleProviderProps { readonly children: ReactNode - readonly locale: ClientLanguageCode + readonly locale: LanguageCode } // Use client languages as the source of truth for supported locales -const SUPPORTED_LOCALES: readonly ClientLanguageCode[] = clientLanguageKeys as ClientLanguageCode[] +const SUPPORTED_LOCALES: readonly LanguageCode[] = languageKeys as LanguageCode[] /** * Validates if a string is a supported locale */ -function isValidLocale(locale: string): locale is ClientLanguageCode { - return clientLanguageKeys.includes(locale) +function isValidLocale(locale: string): locale is LanguageCode { + return languageKeys.includes(locale) } /** - * Gets display name for a locale from client languages data + * Gets display name for a locale from languages module */ -function getLocaleDisplayName(locale: ClientLanguageCode): string { - return clientLanguages[locale]?.name || locale +function getLocaleDisplayName(locale: LanguageCode): string { + return languages[locale]?.name || locale } /** - * Gets native name for a locale from client languages data + * Gets native name for a locale from languages module */ -function getLocaleNativeName(locale: ClientLanguageCode): string { - return clientLanguages[locale]?.nativeName || clientLanguages[locale]?.name || locale +function getLocaleNativeName(locale: LanguageCode): string { + return languages[locale]?.nativeName || languages[locale]?.name || locale } /** * Gets browser language preference as a valid locale */ -function getBrowserLocale(): ClientLanguageCode { +function getBrowserLocale(): LanguageCode { if (typeof window === 'undefined') return 'en' const browserLang = window.navigator.language.split('-')[0] @@ -77,7 +73,7 @@ export function LocaleProvider({ children, locale }: LocaleProviderProps): JSX.E /** * Hook to get current locale with enhanced error handling */ -export function useLocale(): ClientLanguageCode { +export function useLocale(): LanguageCode { const context = useContext(LocaleContext) if (context) { @@ -118,4 +114,4 @@ export function useLocaleContext(): LocaleContextType { } export { isValidLocale, getLocaleDisplayName, getLocaleNativeName } -export type { LocaleContextType, ClientLanguageCode } +export type { LocaleContextType, LanguageCode } diff --git a/src/app/lib/server-context-utils.ts b/src/app/lib/server-context-utils.ts index c389b14b11..7bb74149c2 100644 --- a/src/app/lib/server-context-utils.ts +++ b/src/app/lib/server-context-utils.ts @@ -1,11 +1,11 @@ import { extractLanguageFromPath } from '@/app/lib/language-utils' import { extractVersionFromPath } from '@/app/lib/version-utils' import { getUIDataMerged } from '@/data-directory/lib/get-data' -import { type ClientLanguageCode } from '@/languages/lib/client-languages' +import { type LanguageCode } from '@/languages/lib/languages' import { createTranslationFunctions, translate } from '@/languages/lib/translation-utils' export interface ServerAppRouterContext { - currentLanguage: ClientLanguageCode + currentLanguage: LanguageCode currentVersion: string sitename: string site: { data: { ui: any } } @@ -33,7 +33,7 @@ export function createServerAppRouterContext(pathname: string): ServerAppRouterC /** * Create server-side footer with translations */ -export function createServerFooterContent(language: ClientLanguageCode) { +export function createServerFooterContent(language: LanguageCode) { const uiData = getUIDataMerged(language) const { t } = createTranslationFunctions(uiData, 'footer') diff --git a/src/app/lib/use-detect-locale.tsx b/src/app/lib/use-detect-locale.tsx index 8b82d19b58..c367b9ef51 100644 --- a/src/app/lib/use-detect-locale.tsx +++ b/src/app/lib/use-detect-locale.tsx @@ -2,24 +2,24 @@ import { usePathname } from 'next/navigation' import { useMemo, useEffect, useState } from 'react' -import { clientLanguageKeys, type ClientLanguageCode } from '@/languages/lib/client-languages' +import { languageKeys, type LanguageCode } from '@/languages/lib/languages' import Cookies from '@/frame/components/lib/cookies' import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants' /** * Validates if a string is a supported locale using client languages */ -function isValidLocale(locale: string): locale is ClientLanguageCode { - return clientLanguageKeys.includes(locale) +function isValidLocale(locale: string): locale is LanguageCode { + return languageKeys.includes(locale) } /** * Hook to detect locale from various sources with fallback logic */ -export function useDetectLocale(): ClientLanguageCode { +export function useDetectLocale(): LanguageCode { const pathname = usePathname() - const [cookieLanguage, setCookieLanguage] = useState(null) - const [browserLanguage, setBrowserLanguage] = useState(null) + const [cookieLanguage, setCookieLanguage] = useState(null) + const [browserLanguage, setBrowserLanguage] = useState(null) // Read cookie and browser language on client-side mount useEffect(() => { @@ -71,7 +71,7 @@ export function useDetectLocale(): ClientLanguageCode { /** * Utility function to detect locale from pathname (for server-side use) */ -export function detectLocaleFromPath(pathname: string): ClientLanguageCode { +export function detectLocaleFromPath(pathname: string): LanguageCode { const pathSegments = pathname.split('/') const firstSegment = pathSegments[1] @@ -82,8 +82,8 @@ export function detectLocaleFromPath(pathname: string): ClientLanguageCode { return 'en' } -export function getSupportedLocales(): readonly ClientLanguageCode[] { - return clientLanguageKeys as ClientLanguageCode[] +export function getSupportedLocales(): readonly LanguageCode[] { + return languageKeys as LanguageCode[] } export { isValidLocale } diff --git a/src/app/types.ts b/src/app/types.ts index c86401e08c..bfa7ce13fe 100644 --- a/src/app/types.ts +++ b/src/app/types.ts @@ -2,14 +2,14 @@ * Enhanced type definitions for the app router with strict validation */ -import type { ClientLanguageCode } from '@/languages/lib/client-languages' +import type { LanguageCode } from '@/languages/lib/languages' // Core theme types with strict validation export type Theme = 'light' | 'dark' | 'auto' export type ColorMode = 'light' | 'dark' -// Re-export locale type from client-languages for consistency -export type Locale = ClientLanguageCode +// Re-export locale type from languages.ts for consistency +export type Locale = LanguageCode // Version and product identifiers with validation export type VersionId = string diff --git a/src/archives/middleware/archived-enterprise-versions.ts b/src/archives/middleware/archived-enterprise-versions.ts index 35f65e00e6..fd2be04e92 100644 --- a/src/archives/middleware/archived-enterprise-versions.ts +++ b/src/archives/middleware/archived-enterprise-versions.ts @@ -14,7 +14,7 @@ import { isArchivedVersion } from '@/archives/lib/is-archived-version' import { setFastlySurrogateKey, SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key' import { readCompressedJsonFileFallbackLazily } from '@/frame/lib/read-json-file' import { archivedCacheControl, languageCacheControl } from '@/frame/middleware/cache-control' -import { pathLanguagePrefixed, languagePrefixPathRegex } from '@/languages/lib/languages' +import { pathLanguagePrefixed, languagePrefixPathRegex } from '@/languages/lib/languages-server' import getRedirect, { splitPathByLanguage } from '@/redirects/lib/get-redirect' import getRemoteJSON from '@/frame/lib/get-remote-json' import { ExtendedRequest } from '@/types' diff --git a/src/article-api/scripts/precompute-pageinfo.ts b/src/article-api/scripts/precompute-pageinfo.ts index 655e96c22c..e1a73c49d9 100644 --- a/src/article-api/scripts/precompute-pageinfo.ts +++ b/src/article-api/scripts/precompute-pageinfo.ts @@ -30,7 +30,7 @@ import { brotliCompressSync } from 'zlib' import chalk from 'chalk' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { loadPages, loadUnversionedTree } from '@/frame/lib/page-data' import { CACHE_FILE_PATH, getPageInfo } from '../middleware/article-pageinfo' diff --git a/src/assets/scripts/find-orphaned-assets.ts b/src/assets/scripts/find-orphaned-assets.ts index 9180fdbc9e..7b9aa2dbff 100755 --- a/src/assets/scripts/find-orphaned-assets.ts +++ b/src/assets/scripts/find-orphaned-assets.ts @@ -11,7 +11,7 @@ import { program } from 'commander' import walk from 'walk-sync' import walkFiles from '@/workflows/walk-files' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' const EXCEPTIONS = new Set([ 'assets/images/site/favicon.ico', diff --git a/src/codeql-cli/scripts/convert-markdown-for-docs.ts b/src/codeql-cli/scripts/convert-markdown-for-docs.ts index 316116daa1..af583ed05a 100644 --- a/src/codeql-cli/scripts/convert-markdown-for-docs.ts +++ b/src/codeql-cli/scripts/convert-markdown-for-docs.ts @@ -7,7 +7,7 @@ import { visitParents } from 'unist-util-visit-parents' import { visit, SKIP } from 'unist-util-visit' import { remove } from 'unist-util-remove' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { MARKDOWN_OPTIONS } from '../../content-linter/lib/helpers/unified-formatter-options' interface Config { diff --git a/src/content-linter/lib/linting-rules/internal-links-no-lang.ts b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts index d6f318aa43..fbaabf9cb5 100644 --- a/src/content-linter/lib/linting-rules/internal-links-no-lang.ts +++ b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts @@ -2,7 +2,7 @@ import { filterTokens } from 'markdownlint-rule-helpers' import { addFixErrorDetail, getRange } from '../helpers/utils' -import { allLanguageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages' import type { RuleParams, RuleErrorCallback, Rule } from '../../types' export const internalLinksNoLang: Rule = { @@ -28,7 +28,7 @@ export const internalLinksNoLang: Rule = { .filter((attr: [string, string]) => attr[1].startsWith('/') || !attr[1].startsWith('//')) // Filter out link paths that start with language code .filter((attr: [string, string]) => - allLanguageKeys.some((lang) => attr[1].split('/')[1] === lang), + languageKeys.some((lang) => attr[1].split('/')[1] === lang), ) // Get the link path from the attribute .map((attr: [string, string]) => attr[1]) diff --git a/src/content-linter/scripts/lint-content.ts b/src/content-linter/scripts/lint-content.ts index 1243a7c375..aeca69d6ce 100755 --- a/src/content-linter/scripts/lint-content.ts +++ b/src/content-linter/scripts/lint-content.ts @@ -15,7 +15,7 @@ import { defaultConfig } from '../lib/default-markdownlint-options' import { prettyPrintResults } from './pretty-print-results' import { getLintableYml } from '@/content-linter/lib/helpers/get-lintable-yml' import { printAnnotationResults } from '../lib/helpers/print-annotations' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { shouldIncludeResult } from '../lib/helpers/should-include-result' program diff --git a/src/content-linter/tests/lint-files.ts b/src/content-linter/tests/lint-files.ts index e2e62f5252..f0ad3f5c4e 100755 --- a/src/content-linter/tests/lint-files.ts +++ b/src/content-linter/tests/lint-files.ts @@ -9,7 +9,7 @@ import walk from 'walk-sync' import { zip } from 'lodash-es' import { beforeAll, describe, expect, test } from 'vitest' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { getDiffFiles } from '../lib/diff-files' const __dirname = path.dirname(fileURLToPath(import.meta.url)) diff --git a/src/content-render/scripts/all-documents/cli.ts b/src/content-render/scripts/all-documents/cli.ts index db32f513bc..45c7701cb3 100644 --- a/src/content-render/scripts/all-documents/cli.ts +++ b/src/content-render/scripts/all-documents/cli.ts @@ -43,7 +43,7 @@ import { writeFileSync, statSync } from 'fs' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' import { allDocuments, POSSIBLE_FIELDS, type AllDocument } from './lib' diff --git a/src/content-render/tests/data.ts b/src/content-render/tests/data.ts index b55f997d02..78218c0947 100644 --- a/src/content-render/tests/data.ts +++ b/src/content-render/tests/data.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest' import Page from '@/frame/lib/page' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version' import { DataDirectory } from '@/tests/helpers/data-directory' diff --git a/src/content-render/tests/liquid-helpers.ts b/src/content-render/tests/liquid-helpers.ts index 57a9a863b4..9cc9fd671b 100644 --- a/src/content-render/tests/liquid-helpers.ts +++ b/src/content-render/tests/liquid-helpers.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' import { liquid } from '@/content-render/index' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { DataDirectory } from '@/tests/helpers/data-directory' describe('liquid helper tags', () => { diff --git a/src/data-directory/lib/get-data.ts b/src/data-directory/lib/get-data.ts index 6478c8f3ec..55118a8eaa 100644 --- a/src/data-directory/lib/get-data.ts +++ b/src/data-directory/lib/get-data.ts @@ -5,7 +5,7 @@ import yaml from 'js-yaml' import matter from '@gr2m/gray-matter' import { merge, get } from 'lodash-es' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' interface YAMLException extends Error { diff --git a/src/data-directory/scripts/find-orphaned-features/delete.ts b/src/data-directory/scripts/find-orphaned-features/delete.ts index fe2d8964f6..bf39b57730 100644 --- a/src/data-directory/scripts/find-orphaned-features/delete.ts +++ b/src/data-directory/scripts/find-orphaned-features/delete.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import chalk from 'chalk' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' type Options = { verbose?: boolean diff --git a/src/data-directory/scripts/find-orphaned-features/find.ts b/src/data-directory/scripts/find-orphaned-features/find.ts index 91164d42fd..bce2bb69b6 100644 --- a/src/data-directory/scripts/find-orphaned-features/find.ts +++ b/src/data-directory/scripts/find-orphaned-features/find.ts @@ -38,7 +38,7 @@ import type { Page } from '@/types' import warmServer from '@/frame/lib/warm-server' import { getDeepDataByLanguage } from '@/data-directory/lib/get-data' import { getLiquidTokens } from '@/content-linter/lib/helpers/liquid-utils' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' const EXCEPTIONS = new Set([ diff --git a/src/data-directory/tests/get-data.ts b/src/data-directory/tests/get-data.ts index df7f5dfcc4..cddbeac7e5 100644 --- a/src/data-directory/tests/get-data.ts +++ b/src/data-directory/tests/get-data.ts @@ -3,7 +3,7 @@ import path from 'path' import { afterAll, beforeAll, describe, expect, test } from 'vitest' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { getDataByLanguage, getDeepDataByLanguage, diff --git a/src/early-access/tests/early-access-unit.ts b/src/early-access/tests/early-access-unit.ts index 3f6fc95257..d133f1479a 100644 --- a/src/early-access/tests/early-access-unit.ts +++ b/src/early-access/tests/early-access-unit.ts @@ -2,7 +2,7 @@ import { expect, test, vi } from 'vitest' import { get, getDOM } from '@/tests/helpers/e2etest' import { describeIfDocsEarlyAccess } from '@/tests/helpers/conditional-runs' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' const VALID_EARLY_ACCESS_URI = '/early-access/github/save-time-with-slash-commands' diff --git a/src/events/lib/schema.ts b/src/events/lib/schema.ts index 95a52b6c81..92e72c7511 100644 --- a/src/events/lib/schema.ts +++ b/src/events/lib/schema.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersionKeys } from '@/versions/lib/all-versions' import { productIds } from '@/products/lib/all-products' import { allTools } from '@/tools/lib/all-tools' diff --git a/src/frame/lib/page-data.ts b/src/frame/lib/page-data.ts index 6f0d204705..560fb57ac9 100644 --- a/src/frame/lib/page-data.ts +++ b/src/frame/lib/page-data.ts @@ -1,9 +1,9 @@ import path from 'path' +import languages from '@/languages/lib/languages-server' import type { Language } from '@/languages/lib/languages' import type { UnversionedTree, UnversionLanguageTree, SiteTree, Tree } from '@/types' -import languages from '@/languages/lib/languages' import { allVersions } from '@/versions/lib/all-versions' import createTree from './create-tree' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version' diff --git a/src/frame/middleware/context/context.ts b/src/frame/middleware/context/context.ts index 925c89cd2e..ef4caa955a 100644 --- a/src/frame/middleware/context/context.ts +++ b/src/frame/middleware/context/context.ts @@ -2,7 +2,7 @@ import type { NextFunction, Response } from 'express' import type { ExtendedRequest, Context } from '@/types' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases' import { allVersions } from '@/versions/lib/all-versions' import { productMap } from '@/products/lib/all-products' diff --git a/src/frame/middleware/context/product-groups.ts b/src/frame/middleware/context/product-groups.ts index 0c88acfc34..e6f8e4d8f2 100644 --- a/src/frame/middleware/context/product-groups.ts +++ b/src/frame/middleware/context/product-groups.ts @@ -3,7 +3,7 @@ import type { Response, NextFunction } from 'express' import type { ExtendedRequest } from '@/types' import { getProductGroups } from '@/products/lib/get-product-groups' import warmServer from '@/frame/lib/warm-server' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersionKeys } from '@/versions/lib/all-versions' const isHomepage = (path: string) => { diff --git a/src/frame/middleware/find-page.ts b/src/frame/middleware/find-page.ts index 99d7e75704..00b0013da8 100644 --- a/src/frame/middleware/find-page.ts +++ b/src/frame/middleware/find-page.ts @@ -4,7 +4,7 @@ import type { Response, NextFunction } from 'express' import { ROOT } from '@/frame/lib/constants' import Page from '@/frame/lib/page' -import { languagePrefixPathRegex } from '@/languages/lib/languages' +import { languagePrefixPathRegex } from '@/languages/lib/languages-server' import type { ExtendedRequest } from '@/types' interface FindPageOptions { diff --git a/src/frame/middleware/helmet.ts b/src/frame/middleware/helmet.ts index 9cf66d2c69..978ed674d8 100644 --- a/src/frame/middleware/helmet.ts +++ b/src/frame/middleware/helmet.ts @@ -1,6 +1,6 @@ import { shouldUseAppRouter, isVersionedPath } from '@/app/lib/routing-patterns' import { isArchivedVersion } from '@/archives/lib/is-archived-version' -import { languagePrefixPathRegex } from '@/languages/lib/languages' +import { languagePrefixPathRegex } from '@/languages/lib/languages-server' import versionSatisfiesRange from '@/versions/lib/version-satisfies-range' import type { NextFunction, Request, Response } from 'express' import helmet from 'helmet' diff --git a/src/frame/middleware/llms-txt.ts b/src/frame/middleware/llms-txt.ts index 975162fde4..88020d11ec 100644 --- a/src/frame/middleware/llms-txt.ts +++ b/src/frame/middleware/llms-txt.ts @@ -5,7 +5,7 @@ import type { ExtendedRequest } from '@/types' import { defaultCacheControl } from '@/frame/middleware/cache-control' import catchMiddlewareError from '@/observability/middleware/catch-middleware-error' import statsd from '@/observability/lib/statsd' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' const router = express.Router() diff --git a/src/frame/middleware/reload-tree.ts b/src/frame/middleware/reload-tree.ts index 04513c3dda..2f1c4ff9a4 100644 --- a/src/frame/middleware/reload-tree.ts +++ b/src/frame/middleware/reload-tree.ts @@ -19,7 +19,7 @@ import path from 'path' import type { Response, NextFunction } from 'express' import type { ExtendedRequest, UnversionedTree, SiteTree } from '@/types' -import languages, { languageKeys } from '@/languages/lib/languages' +import languages, { languageKeys } from '@/languages/lib/languages-server' import createTree from '@/frame/lib/create-tree' import warmServer from '@/frame/lib/warm-server' import { loadSiteTree, loadPages, loadPageMap } from '@/frame/lib/page-data' diff --git a/src/frame/tests/pages.ts b/src/frame/tests/pages.ts index 76ed444e4b..af8c4efc41 100644 --- a/src/frame/tests/pages.ts +++ b/src/frame/tests/pages.ts @@ -6,7 +6,7 @@ import { decode } from 'html-entities' import { chain, pick } from 'lodash-es' import { loadPages } from '@/frame/lib/page-data' -import libLanguages from '@/languages/lib/languages' +import libLanguages from '@/languages/lib/languages-server' import { liquid } from '@/content-render/index' import patterns from '@/frame/lib/patterns' import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path' diff --git a/src/ghes-releases/scripts/deprecate/archive-version.ts b/src/ghes-releases/scripts/deprecate/archive-version.ts index 91107319c4..e3f0c3435b 100755 --- a/src/ghes-releases/scripts/deprecate/archive-version.ts +++ b/src/ghes-releases/scripts/deprecate/archive-version.ts @@ -17,7 +17,7 @@ import createApp from '@/frame/lib/app' import EnterpriseServerReleases from '@/versions/lib/enterprise-server-releases' import loadRedirects from '@/redirects/lib/precompile' import { loadPageMap, loadPages } from '@/frame/lib/page-data' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { RewriteAssetPathsPlugin } from '@/ghes-releases/scripts/deprecate/rewrite-asset-paths' import Page from '@/frame/lib/page' diff --git a/src/graphql/lib/index.ts b/src/graphql/lib/index.ts index d7c39a43d1..9358176184 100644 --- a/src/graphql/lib/index.ts +++ b/src/graphql/lib/index.ts @@ -3,7 +3,7 @@ import { readCompressedJsonFileFallback, } from '@/frame/lib/read-json-file' import { getAutomatedPageMiniTocItems } from '@/frame/lib/get-mini-toc-items' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' interface GraphqlContext { currentLanguage: string diff --git a/src/languages/lib/client-languages.ts b/src/languages/lib/client-languages.ts deleted file mode 100644 index c238b09032..0000000000 --- a/src/languages/lib/client-languages.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { LanguageItem } from '@/languages/components/LanguagesContext' - -/** - * Client-safe language data extracted from src/languages/lib/languages.ts. - * Only used by frontend components. - * Does not include server-side logic or Node.js-specific fs or path operations. - */ -export const clientLanguages: Record = { - en: { - name: 'English', - code: 'en', - nativeName: 'English', - hreflang: 'en', - }, - es: { - name: 'Spanish', - code: 'es', - nativeName: 'Español', - hreflang: 'es', - }, - ja: { - name: 'Japanese', - code: 'ja', - nativeName: '日本語', - hreflang: 'ja', - }, - pt: { - name: 'Portuguese', - code: 'pt', - nativeName: 'Português do Brasil', - hreflang: 'pt', - }, - zh: { - name: 'Simplified Chinese', - code: 'zh', - nativeName: '简体中文', - hreflang: 'zh-Hans', - }, - ru: { - name: 'Russian', - code: 'ru', - nativeName: 'Русский', - hreflang: 'ru', - }, - fr: { - name: 'French', - code: 'fr', - nativeName: 'Français', - hreflang: 'fr', - }, - ko: { - name: 'Korean', - code: 'ko', - nativeName: '한국어', - hreflang: 'ko', - }, - de: { - name: 'German', - code: 'de', - nativeName: 'Deutsch', - hreflang: 'de', - }, -} - -export const clientLanguageKeys: string[] = Object.keys(clientLanguages) - -export type ClientLanguageCode = keyof typeof clientLanguages diff --git a/src/languages/lib/get-alert-titles.ts b/src/languages/lib/get-alert-titles.ts index 52406abb62..d73e33281c 100644 --- a/src/languages/lib/get-alert-titles.ts +++ b/src/languages/lib/get-alert-titles.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises' import path from 'path' import yaml from 'js-yaml' -import languages from './languages' +import languages from './languages-server' const cache: Record = {} diff --git a/src/languages/lib/languages-server.ts b/src/languages/lib/languages-server.ts new file mode 100644 index 0000000000..97e41f2ff2 --- /dev/null +++ b/src/languages/lib/languages-server.ts @@ -0,0 +1,97 @@ +/* +This file adds the following properties to languages in ./languages.ts: +- dir: string + +This file will also remove languages for local development and tests +that have not be specified by ENABLED_LANGUAGES +*/ + +import path from 'path' +import fs from 'fs' +import dotenv from 'dotenv' + +import { ROOT, TRANSLATIONS_ROOT, TRANSLATIONS_FIXTURE_ROOT } from '@/frame/lib/constants' +import { languages as baseLanguages, type Language as BaseLanguage } from './languages' + +dotenv.config({ quiet: true }) + +// Server-side language extends base language with required dir property +export interface Language extends BaseLanguage { + dir: string +} + +export interface Languages { + [code: string]: Language +} + +function getRoot(languageCode: string): string { + if (languageCode === 'en') return ROOT + + // This one trumps anything else. This makes it possible, and convenient, + // for running tests that depends on testing translations based on + // fixtures exclusively. + if (TRANSLATIONS_FIXTURE_ROOT) { + return path.join(TRANSLATIONS_FIXTURE_ROOT, languageCode) + } + + // example: process.env.TRANSLATIONS_ROOT_ES_ES + const possibleEnvVar = + process.env[`TRANSLATIONS_ROOT_${languageCode.toUpperCase().replace(/-/g, '_')}`] + if (possibleEnvVar) { + return possibleEnvVar + } + + // Default + return path.join(TRANSLATIONS_ROOT, languageCode) +} + +// Build server languages with directory paths +const allLanguagesWithDirs: Languages = {} +for (const [code, lang] of Object.entries(baseLanguages)) { + allLanguagesWithDirs[code] = { + ...lang, + dir: getRoot(lang.locale || code), + } +} + +Object.freeze(allLanguagesWithDirs) + +const languages: Languages = { ...allLanguagesWithDirs } + +if (TRANSLATIONS_FIXTURE_ROOT) { + // Keep all languages that have a directory in the fixture root. + Object.entries(languages).forEach(([code, { dir }]) => { + if (code !== 'en' && !fs.existsSync(dir)) { + delete languages[code] + } + }) +} else if (process.env.ENABLED_LANGUAGES) { + if (process.env.ENABLED_LANGUAGES.toLowerCase() !== 'all') { + Object.keys(languages).forEach((code) => { + if (!process.env.ENABLED_LANGUAGES!.includes(code)) { + delete languages[code] + } + }) + // This makes the translation health report not valid JSON + // console.log(`ENABLED_LANGUAGES: ${process.env.ENABLED_LANGUAGES}`) + } +} else if (process.env.NODE_ENV === 'test') { + // Unless explicitly set, when running tests default to just English + Object.keys(languages).forEach((code) => { + if (code !== 'en') delete languages[code] + }) +} + +export const languageKeys: string[] = Object.keys(languages) + +export const languagePrefixPathRegex: RegExp = new RegExp(`^/(${languageKeys.join('|')})(/|$)`) + +/** Return true if the URL is something like /en/foo or /ja but return false + * if it's something like /foo or /foo/bar or /fr (because French (fr) + * is currently not an active language) + */ +export function pathLanguagePrefixed(path: string): boolean { + return languagePrefixPathRegex.test(path) +} + +export default languages diff --git a/src/languages/lib/languages.ts b/src/languages/lib/languages.ts index ce1fd0538e..049b032241 100644 --- a/src/languages/lib/languages.ts +++ b/src/languages/lib/languages.ts @@ -1,26 +1,15 @@ // See also languages-schema.ts // Nota bene: If you are adding a new language, // change accept-language handling in CDN config as well. -import path from 'path' -import fs from 'fs' -import dotenv from 'dotenv' - -import { ROOT, TRANSLATIONS_ROOT, TRANSLATIONS_FIXTURE_ROOT } from '@/frame/lib/constants' - -dotenv.config({ quiet: true }) - -export interface Language { - name: string - nativeName?: string - code: string - hreflang: string - redirectPatterns?: RegExp[] - dir: string -} +/** + * Client-safe language definitions without server-side dependencies. + * For server-side usage with fs/path operations, import from './languages-server.ts' + */ export type LanguageCode = 'en' | 'es' | 'ja' | 'pt' | 'zh' | 'ru' | 'fr' | 'ko' | 'de' export type LocaleCode = + | 'en' | 'es-es' | 'ja-jp' | 'pt-br' @@ -30,57 +19,35 @@ export type LocaleCode = | 'ko-kr' | 'de-de' +export interface Language { + name: string + nativeName?: string + code: LanguageCode + hreflang: string + locale: LocaleCode + redirectPatterns?: RegExp[] + dir?: string +} + export interface Languages { [code: string]: Language } -const possibleEnvVars: Record = { - 'es-es': process.env.TRANSLATIONS_ROOT_ES_ES, - 'ja-jp': process.env.TRANSLATIONS_ROOT_JA_JP, - 'pt-br': process.env.TRANSLATIONS_ROOT_PT_BR, - 'zh-cn': process.env.TRANSLATIONS_ROOT_ZH_CN, - 'ru-ru': process.env.TRANSLATIONS_ROOT_RU_RU, - 'fr-fr': process.env.TRANSLATIONS_ROOT_FR_FR, - 'ko-kr': process.env.TRANSLATIONS_ROOT_KO_KR, - 'de-de': process.env.TRANSLATIONS_ROOT_DE_DE, -} - -function getRoot(languageCode: string): string { - if (languageCode === 'en') return ROOT - - // This one trumps anything else. This makes it possible, and convenient, - // for running tests that depends on testing translations based on - // fixtures exclusively. - if (TRANSLATIONS_FIXTURE_ROOT) { - return path.join(TRANSLATIONS_FIXTURE_ROOT, languageCode) - } - - if (languageCode in possibleEnvVars) { - const possibleEnvVar = possibleEnvVars[languageCode as LocaleCode] - if (possibleEnvVar) { - return possibleEnvVar - } - } else { - console.warn(`Not recognized languageCode '${languageCode}'`) - } - // Default - return path.join(TRANSLATIONS_ROOT, languageCode) -} - // Languages in order of accept-language header frequency -const allLanguages: Languages = { +// Note: 'dir' is omitted here as it requires server-side path resolution +export const languages: Languages = { en: { name: 'English', code: 'en', hreflang: 'en', - dir: getRoot('en'), + locale: 'en', }, es: { name: 'Spanish', nativeName: 'Español', code: 'es', hreflang: 'es', - dir: getRoot('es-es'), + locale: 'es-es', }, ja: { name: 'Japanese', @@ -88,7 +55,7 @@ const allLanguages: Languages = { code: 'ja', hreflang: 'ja', redirectPatterns: [/^\/jp/], - dir: getRoot('ja-jp'), + locale: 'ja-jp', }, pt: { name: 'Portuguese', @@ -96,7 +63,7 @@ const allLanguages: Languages = { code: 'pt', hreflang: 'pt', redirectPatterns: [/^\/br/], - dir: getRoot('pt-br'), + locale: 'pt-br', }, zh: { name: 'Simplified Chinese', @@ -104,21 +71,21 @@ const allLanguages: Languages = { code: 'zh', hreflang: 'zh-Hans', redirectPatterns: [/^\/cn/, /^\/zh-\w{2}/], - dir: getRoot('zh-cn'), + locale: 'zh-cn', }, ru: { name: 'Russian', nativeName: 'Русский', code: 'ru', hreflang: 'ru', - dir: getRoot('ru-ru'), + locale: 'ru-ru', }, fr: { name: 'French', nativeName: 'Français', code: 'fr', hreflang: 'fr', - dir: getRoot('fr-fr'), + locale: 'fr-fr', }, ko: { name: 'Korean', @@ -126,59 +93,16 @@ const allLanguages: Languages = { code: 'ko', hreflang: 'ko', redirectPatterns: [/^\/kr/], - dir: getRoot('ko-kr'), + locale: 'ko-kr', }, de: { name: 'German', nativeName: 'Deutsch', code: 'de', hreflang: 'de', - dir: getRoot('de-de'), + locale: 'de-de', }, } -// Some markdownlint tests depend on having access to all -// language keys. Not modifying the original object makes -// it possible to export all keys, even when those directories -// don't exist on disk. -Object.freeze(allLanguages) -export const allLanguageKeys: string[] = Object.keys(allLanguages) -const languages: Languages = { ...allLanguages } - -if (TRANSLATIONS_FIXTURE_ROOT) { - // Keep all languages that have a directory in the fixture root. - Object.entries(languages).forEach(([code, { dir }]) => { - if (code !== 'en' && !fs.existsSync(dir)) { - delete languages[code] - } - }) -} else if (process.env.ENABLED_LANGUAGES) { - if (process.env.ENABLED_LANGUAGES.toLowerCase() !== 'all') { - Object.keys(languages).forEach((code) => { - if (!process.env.ENABLED_LANGUAGES!.includes(code)) { - delete languages[code] - } - }) - // This makes the translation health report not valid JSON - // console.log(`ENABLED_LANGUAGES: ${process.env.ENABLED_LANGUAGES}`) - } -} else if (process.env.NODE_ENV === 'test') { - // Unless explicitly set, when running tests default to just English - Object.keys(languages).forEach((code) => { - if (code !== 'en') delete languages[code] - }) -} - +Object.freeze(languages) export const languageKeys: string[] = Object.keys(languages) - -export const languagePrefixPathRegex: RegExp = new RegExp(`^/(${languageKeys.join('|')})(/|$)`) - -/** Return true if the URL is something like /en/foo or /ja but return false - * if it's something like /foo or /foo/bar or /fr (because French (fr) - * is currently not an active language) - */ -export function pathLanguagePrefixed(path: string): boolean { - return languagePrefixPathRegex.test(path) -} - -export default languages diff --git a/src/languages/middleware/detect-language.ts b/src/languages/middleware/detect-language.ts index 937096580f..c54b3f1469 100644 --- a/src/languages/middleware/detect-language.ts +++ b/src/languages/middleware/detect-language.ts @@ -2,7 +2,7 @@ import type { Request, Response, NextFunction } from 'express' import parser from 'accept-language-parser' import type { Language as parserLanguage } from 'accept-language-parser' -import languages, { languageKeys } from '@/languages/lib/languages' +import languages, { languageKeys } from '@/languages/lib/languages-server' import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants' import type { ExtendedRequest, Languages } from '@/types' import { updateLoggerContext } from '@/observability/logger/lib/logger-context' diff --git a/src/languages/scripts/count-translation-corruptions.ts b/src/languages/scripts/count-translation-corruptions.ts index 4315db629f..9c7221c850 100644 --- a/src/languages/scripts/count-translation-corruptions.ts +++ b/src/languages/scripts/count-translation-corruptions.ts @@ -7,7 +7,7 @@ import { TokenizationError } from 'liquidjs' import walk from 'walk-sync' import { getLiquidTokens } from '@/content-linter/lib/helpers/liquid-utils' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import warmServer from '@/frame/lib/warm-server' import type { Site } from '@/types' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' diff --git a/src/languages/scripts/purge-fastly-edge-cache-per-language.ts b/src/languages/scripts/purge-fastly-edge-cache-per-language.ts index 3bd868240e..bb4f2fa175 100644 --- a/src/languages/scripts/purge-fastly-edge-cache-per-language.ts +++ b/src/languages/scripts/purge-fastly-edge-cache-per-language.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { makeLanguageSurrogateKey } from '@/frame/middleware/set-fastly-surrogate-key' import purgeEdgeCache from '@/workflows/purge-edge-cache' diff --git a/src/languages/tests/files.ts b/src/languages/tests/files.ts index b0508c436f..0f825cb386 100644 --- a/src/languages/tests/files.ts +++ b/src/languages/tests/files.ts @@ -1,4 +1,4 @@ -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { describe, expect, test, vi } from 'vitest' describe('files', () => { diff --git a/src/languages/tests/frame.ts b/src/languages/tests/frame.ts index 4140d36c19..55122e69d7 100644 --- a/src/languages/tests/frame.ts +++ b/src/languages/tests/frame.ts @@ -1,6 +1,6 @@ import { describe, expect, test, vi } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { blockIndex } from '@/frame/middleware/block-robots' import { get, getDOMCached as getDOM } from '@/tests/helpers/e2etest' import Page from '@/frame/lib/page' diff --git a/src/languages/tests/glossary.ts b/src/languages/tests/glossary.ts index 5b3e58b3ad..2f7372d267 100644 --- a/src/languages/tests/glossary.ts +++ b/src/languages/tests/glossary.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { getDOM } from '@/tests/helpers/e2etest' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/languages/tests/llms-txt-translations.ts b/src/languages/tests/llms-txt-translations.ts index 1b4011f576..e386c60ac0 100644 --- a/src/languages/tests/llms-txt-translations.ts +++ b/src/languages/tests/llms-txt-translations.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' import { get } from '@/tests/helpers/e2etest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/languages/tests/redirects.ts b/src/languages/tests/redirects.ts index f718ee7413..f3d9aaa242 100644 --- a/src/languages/tests/redirects.ts +++ b/src/languages/tests/redirects.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { get } from '@/tests/helpers/e2etest' import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants' diff --git a/src/languages/tests/search.ts b/src/languages/tests/search.ts index 403cb5df0b..ee1477b5f5 100644 --- a/src/languages/tests/search.ts +++ b/src/languages/tests/search.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { get } from '@/tests/helpers/e2etest' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/products/lib/get-product-groups.ts b/src/products/lib/get-product-groups.ts index 670d61db09..6336dfc7cb 100644 --- a/src/products/lib/get-product-groups.ts +++ b/src/products/lib/get-product-groups.ts @@ -6,7 +6,7 @@ import { productMap, data } from '@/products/lib/all-products' import { renderContentWithFallback } from '@/languages/lib/render-with-fallback' import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path' import frontmatter from '@/frame/lib/read-frontmatter' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' type PageMap = Record diff --git a/src/redirects/lib/get-redirect.ts b/src/redirects/lib/get-redirect.ts index b73780caab..8cc5e1d043 100644 --- a/src/redirects/lib/get-redirect.ts +++ b/src/redirects/lib/get-redirect.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version' import { allVersions } from '@/versions/lib/all-versions' import { diff --git a/src/redirects/middleware/handle-redirects.ts b/src/redirects/middleware/handle-redirects.ts index 907271fe46..82d88882de 100644 --- a/src/redirects/middleware/handle-redirects.ts +++ b/src/redirects/middleware/handle-redirects.ts @@ -1,7 +1,7 @@ import type { NextFunction, Response } from 'express' import patterns from '@/frame/lib/patterns' -import { pathLanguagePrefixed } from '@/languages/lib/languages' +import { pathLanguagePrefixed } from '@/languages/lib/languages-server' import { deprecatedWithFunctionalRedirects } from '@/versions/lib/enterprise-server-releases' import getRedirect from '../lib/get-redirect' import { defaultCacheControl, languageCacheControl } from '@/frame/middleware/cache-control' diff --git a/src/redirects/middleware/language-code-redirects.ts b/src/redirects/middleware/language-code-redirects.ts index 1ce32056d3..fc45fca9ea 100644 --- a/src/redirects/middleware/language-code-redirects.ts +++ b/src/redirects/middleware/language-code-redirects.ts @@ -1,6 +1,6 @@ import type { NextFunction, Response } from 'express' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { defaultCacheControl } from '@/frame/middleware/cache-control' import { ExtendedRequest } from '@/types' diff --git a/src/rest/lib/index.ts b/src/rest/lib/index.ts index 47d96dfb1c..9702abf218 100644 --- a/src/rest/lib/index.ts +++ b/src/rest/lib/index.ts @@ -4,7 +4,7 @@ import path from 'path' import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file' import { getAutomatedPageMiniTocItems } from '@/frame/lib/get-mini-toc-items' import { allVersions, getOpenApiVersion } from '@/versions/lib/all-versions' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import type { Context } from '@/types' import type { Operation } from '@/rest/components/types' diff --git a/src/search/lib/elasticsearch-indexes.ts b/src/search/lib/elasticsearch-indexes.ts index a8a5ae4359..db6643e7f8 100644 --- a/src/search/lib/elasticsearch-indexes.ts +++ b/src/search/lib/elasticsearch-indexes.ts @@ -1,4 +1,4 @@ -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { utcTimestamp } from '@/search/lib/helpers/time' import { allIndexVersionKeys, versionToIndexVersionMap } from '@/search/lib/elasticsearch-versions' diff --git a/src/search/lib/search-request-params/search-params-objects.ts b/src/search/lib/search-request-params/search-params-objects.ts index 1532e4276f..2db04bbb05 100644 --- a/src/search/lib/search-request-params/search-params-objects.ts +++ b/src/search/lib/search-request-params/search-params-objects.ts @@ -3,7 +3,7 @@ we need to validate and parse the parameters. This file contains the configuration for which parameters to expect based on the type of search request "e.g. general search vs autocomplete search" and how to validate them. */ -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import { allIndexVersionKeys, versionToIndexVersionMap } from '@/search/lib/elasticsearch-versions' import { SearchTypes } from '@/search/types' diff --git a/src/search/scripts/analyze-text.ts b/src/search/scripts/analyze-text.ts index c8025da60a..a2be919aca 100755 --- a/src/search/scripts/analyze-text.ts +++ b/src/search/scripts/analyze-text.ts @@ -10,7 +10,7 @@ import { Command, Option } from 'commander' import chalk from 'chalk' import dotenv from 'dotenv' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { allVersions } from '@/versions/lib/all-versions' import type { IndicesAnalyzeAnalyzeToken } from '@elastic/elasticsearch/lib/api/types' diff --git a/src/search/scripts/index/index-cli.ts b/src/search/scripts/index/index-cli.ts index 1c494dd834..acaa3630af 100644 --- a/src/search/scripts/index/index-cli.ts +++ b/src/search/scripts/index/index-cli.ts @@ -2,7 +2,7 @@ import { program, Option, Command, InvalidArgumentError } from 'commander' import { errors } from '@elastic/elasticsearch' import dotenv from 'dotenv' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { indexGeneralSearch } from './lib/index-general-search' import { diff --git a/src/search/scripts/index/lib/index-general-search.ts b/src/search/scripts/index/lib/index-general-search.ts index 595eedc901..c588489899 100644 --- a/src/search/scripts/index/lib/index-general-search.ts +++ b/src/search/scripts/index/lib/index-general-search.ts @@ -1,6 +1,6 @@ import { Client } from '@elastic/elasticsearch' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import { getElasticSearchIndex } from '@/search/lib/elasticsearch-indexes' import { getElasticsearchClient } from '@/search/lib/helpers/get-client' import { diff --git a/src/search/scripts/scrape/lib/build-records.ts b/src/search/scripts/scrape/lib/build-records.ts index fe082ebd4c..b50ca77240 100644 --- a/src/search/scripts/scrape/lib/build-records.ts +++ b/src/search/scripts/scrape/lib/build-records.ts @@ -3,7 +3,7 @@ import chalk from 'chalk' import dotenv from 'dotenv' import boxen from 'boxen' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import parsePageSectionsIntoRecords from '@/search/scripts/scrape/lib/parse-page-sections-into-records' import getPopularPages from '@/search/scripts/scrape/lib/popular-pages' import domwaiter from '@/search/scripts/scrape/lib/domwaiter' diff --git a/src/search/scripts/scrape/lib/scrape-into-index-json.ts b/src/search/scripts/scrape/lib/scrape-into-index-json.ts index ccb1880c3f..fe609bdcf3 100644 --- a/src/search/scripts/scrape/lib/scrape-into-index-json.ts +++ b/src/search/scripts/scrape/lib/scrape-into-index-json.ts @@ -1,6 +1,6 @@ import chalk from 'chalk' -import languages from '@/languages/lib/languages' +import languages from '@/languages/lib/languages-server' import buildRecords from '@/search/scripts/scrape/lib/build-records' import findIndexablePages from '@/search/scripts/scrape/lib/find-indexable-pages' import { writeIndexRecords } from '@/search/scripts/scrape/lib/search-index-records' diff --git a/src/search/scripts/scrape/scrape-cli.ts b/src/search/scripts/scrape/scrape-cli.ts index 362a295746..717e5da1dd 100644 --- a/src/search/scripts/scrape/scrape-cli.ts +++ b/src/search/scripts/scrape/scrape-cli.ts @@ -4,7 +4,7 @@ import { existsSync, statSync, readdirSync } from 'fs' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages' +import { languageKeys } from '@/languages/lib/languages-server' import scrapeIntoIndexJson from '@/search/scripts/scrape/lib/scrape-into-index-json' import { allIndexVersionOptions, diff --git a/src/types.ts b/src/types.ts index 7aed23911f..1a709d0c8e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import type { Failbot } from '@github/failbot' import type enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.d' import type { ValidOcticon } from '@/landings/types' +import type { Language, Languages } from '@/languages/lib/languages-server' import type { MiniTocItem } from '@/frame/lib/get-mini-toc-items' // Shared type for resolved article information used across landing pages and carousels @@ -325,16 +326,8 @@ export type SecretScanningData = { isduplicate: boolean } -type Language = { - name: string - code: string - hreflang: string - dir: string -} - -export type Languages = { - [key: string]: Language -} +// Language and Languages types are imported at the top from languages-server +export type { Language, Languages } export type Permalink = { languageCode: string