Bläddra i källkod

Fix lang attribute in Html element to correctly reflect locale and ensure it works on route changes

maeshinshin 1 år sedan
förälder
incheckning
432b75f173

+ 29 - 7
apps/app/src/pages/_app.page.tsx

@@ -3,23 +3,24 @@ import React, { useEffect } from 'react';
 
 import type { NextPage } from 'next';
 import { appWithTranslation } from 'next-i18next';
-import type { AppProps } from 'next/app';
+import type { AppContext, AppProps } from 'next/app';
+import App from 'next/app';
+import { useRouter } from 'next/router';
 import { SWRConfig } from 'swr';
 
 import * as nextI18nConfig from '^/config/next-i18next.config';
 
 import { GlobalFonts } from '~/components/FontFamily/GlobalFonts';
+import type { CrowiRequest } from '~/interfaces/crowi-request';
 import {
   useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useIsDefaultLogo, useForcedColorScheme,
 } from '~/stores-universal/context';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 
-import type { CommonProps } from './utils/commons';
-import { registerTransformerForObjectId } from './utils/objectid-transformer';
-
+import { getLocaleAtServerSide, type CommonProps } from './utils/commons';
 import '~/styles/prebuilt/vendor.css';
 import '~/styles/style-app.scss';
-
+import { registerTransformerForObjectId } from './utils/objectid-transformer';
 
 // eslint-disable-next-line @typescript-eslint/ban-types
 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
@@ -28,17 +29,31 @@ export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
 
 type GrowiAppProps = AppProps & {
   Component: NextPageWithLayout,
+  userLocale: string,
 };
 
 // register custom serializer
 registerTransformerForObjectId();
 
-function GrowiApp({ Component, pageProps }: GrowiAppProps): JSX.Element {
+function GrowiApp({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Element {
+  const router = useRouter();
+
+  useEffect(() => {
+    const updateLangAttribute = () => {
+      if (document.documentElement.getAttribute('lang') !== userLocale) {
+        document.documentElement.setAttribute('lang', userLocale);
+      }
+    };
+    router.events.on('routeChangeComplete', updateLangAttribute);
+    return () => {
+      router.events.off('routeChangeComplete', updateLangAttribute);
+    };
+  }, [router, userLocale]);
+
   useEffect(() => {
     import('bootstrap/dist/js/bootstrap');
   }, []);
 
-
   const commonPageProps = pageProps as CommonProps;
   useAppTitle(commonPageProps.appTitle);
   useSiteUrl(commonPageProps.siteUrl);
@@ -60,4 +75,11 @@ function GrowiApp({ Component, pageProps }: GrowiAppProps): JSX.Element {
   );
 }
 
+GrowiApp.getInitialProps = async(appContext: AppContext) => {
+  const appProps = App.getInitialProps(appContext);
+  const userLocale = getLocaleAtServerSide(appContext.ctx.req as unknown as CrowiRequest);
+
+  return { ...appProps, userLocale };
+};
+
 export default appWithTranslation(GrowiApp, nextI18nConfig);

+ 2 - 12
apps/app/src/pages/_document.page.tsx

@@ -1,7 +1,6 @@
 /* eslint-disable @next/next/google-font-display */
 import React from 'react';
 
-import { Lang } from '@growi/core';
 import type { DocumentContext, DocumentInitialProps } from 'next/document';
 import Document, {
   Html, Head, Main, NextScript,
@@ -9,11 +8,9 @@ import Document, {
 
 import type { GrowiPluginResourceEntries } from '~/features/growi-plugin/server/services';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
-import { configManager } from '~/server/service/config-manager';
-import { detectLocaleFromBrowserAcceptLanguage } from '~/server/util/locale-utils';
 import loggerFactory from '~/utils/logger';
 
-import { getLocateAtServerSide } from './utils/commons';
+import { getLocaleAtServerSide } from './utils/commons';
 
 const logger = loggerFactory('growi:page:_document');
 
@@ -54,13 +51,6 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
 
   static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
 
-    const langMap = {
-      [Lang.ja_JP]: 'ja-jp',
-      [Lang.en_US]: 'en-us',
-      [Lang.zh_CN]: 'zh-cn',
-      [Lang.fr_FR]: 'fr-fr',
-    } as const;
-
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
     const req = ctx.req as CrowiRequest;
     const { crowi } = req;
@@ -75,7 +65,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
     const growiPluginService = await import('~/features/growi-plugin/server/services').then(mod => mod.growiPluginService);
     const pluginResourceEntries = await growiPluginService.retrieveAllPluginResourceEntries();
 
-    const locale = langMap[getLocateAtServerSide(req)];
+    const locale = getLocaleAtServerSide(req);
 
     return {
       ...initialProps,

+ 11 - 6
apps/app/src/pages/utils/commons.ts

@@ -5,7 +5,6 @@ import { isServer } from '@growi/core/dist/utils';
 import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import type { SSRConfig, UserConfig } from 'next-i18next';
 
-
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 
 import { type SupportedActionType } from '~/interfaces/activity';
@@ -106,13 +105,19 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   return { props };
 };
 
-
-export const getLocateAtServerSide = (req: CrowiRequest): Lang => {
+export const getLocaleAtServerSide = (req: CrowiRequest): 'ja-jp' | 'en-us' | 'zh-cn' | 'fr-fr' => {
   const { user, headers } = req;
   const { configManager } = req.crowi;
 
-  return user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
-    : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
+  const langMap = {
+    [Lang.ja_JP]: 'ja-jp',
+    [Lang.en_US]: 'en-us',
+    [Lang.zh_CN]: 'zh-cn',
+    [Lang.fr_FR]: 'fr-fr',
+  } as const;
+
+  return langMap[user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
+    : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US) ?? Lang.en_US];
 };
 
 export const getNextI18NextConfig = async(
@@ -126,7 +131,7 @@ export const getNextI18NextConfig = async(
 
   // determine language
   const req: CrowiRequest = context.req as CrowiRequest;
-  const locale = getLocateAtServerSide(req);
+  const locale = getLocaleAtServerSide(req);
 
   const namespaces = ['commons'];
   if (namespacesRequired != null) {