Просмотр исходного кода

Merge pull request #8960 from weseek/imprv/147666-149981-html-lang-attribute

imprv: lang attribute in Html element
Yuki Takei 1 год назад
Родитель
Сommit
b0ef3ef01c

+ 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,

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

@@ -1,11 +1,10 @@
 import type { ColorScheme, IUserHasId } from '@growi/core';
-import { Lang, AllLang } from '@growi/core';
+import { Lang, AllLang, Locale } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
 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,23 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   return { props };
 };
 
+export type LangMap = {
+  readonly [key in Lang]: Locale;
+};
+
+export const langMap: LangMap = {
+  [Lang.ja_JP]: Locale['ja-JP'],
+  [Lang.en_US]: Locale['en-US'],
+  [Lang.zh_CN]: Locale['zh-CN'],
+  [Lang.fr_FR]: Locale['fr-FR'],
+};
 
-export const getLocateAtServerSide = (req: CrowiRequest): Lang => {
+export const getLocaleAtServerSide = (req: CrowiRequest): Locale => {
   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);
+  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 +135,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) {

+ 1 - 0
packages/core/src/interfaces/index.ts

@@ -7,6 +7,7 @@ export * from './growi-facade';
 export * from './growi-theme-metadata';
 export * from './has-object-id';
 export * from './lang';
+export * from './locale';
 export * from './page';
 export * from './revision';
 export * from './subscription';

+ 8 - 0
packages/core/src/interfaces/locale.ts

@@ -0,0 +1,8 @@
+export const Locale = {
+  'en-US': 'en-US',
+  'ja-JP': 'ja-JP',
+  'zh-CN': 'zh-CN',
+  'fr-FR': 'fr-FR',
+} as const;
+export const AllLocale = Object.values(Locale);
+export type Locale = typeof Locale[keyof typeof Locale];