From 66411680fbeac9f519bff1dca1b4086979957f3e Mon Sep 17 00:00:00 2001 From: Artem Tarasov Date: Sun, 13 Mar 2022 22:23:42 +0300 Subject: [PATCH] Web: Doceditor: added i18n implementation on server side --- web/ASC.Web.Editor/next-i18next.config.js | 12 ---- web/ASC.Web.Editor/src/Editor.js | 6 +- web/ASC.Web.Editor/src/client/index.js | 20 +++++- web/ASC.Web.Editor/src/i18n.js | 48 +++++++-------- web/ASC.Web.Editor/src/index.js | 75 ++++++++++++++++++----- web/ASC.Web.Editor/src/server/render.js | 9 ++- web/ASC.Web.Editor/src/server/template.js | 8 ++- 7 files changed, 119 insertions(+), 59 deletions(-) delete mode 100644 web/ASC.Web.Editor/next-i18next.config.js diff --git a/web/ASC.Web.Editor/next-i18next.config.js b/web/ASC.Web.Editor/next-i18next.config.js deleted file mode 100644 index f6f09bf0dc..0000000000 --- a/web/ASC.Web.Editor/next-i18next.config.js +++ /dev/null @@ -1,12 +0,0 @@ -const localesFolder = "./public/locales"; -const fs = require("fs"); - -const availableLocales = fs.readdirSync(localesFolder); - -module.exports = { - i18n: { - defaultLocale: "en", - locales: availableLocales, - defaultNS: "Editor", - }, -}; diff --git a/web/ASC.Web.Editor/src/Editor.js b/web/ASC.Web.Editor/src/Editor.js index c42e4e138a..08896973af 100644 --- a/web/ASC.Web.Editor/src/Editor.js +++ b/web/ASC.Web.Editor/src/Editor.js @@ -18,6 +18,7 @@ import { import { EditorWrapper } from "./StyledEditor"; import DynamicComponent from "./components/dynamic"; +import { useTranslation } from "react-i18next"; const { homepage } = pkg; @@ -148,6 +149,9 @@ export default function Editor({ const [documentTitle, setNewDocumentTitle] = useState("Loading..."); const [faviconHref, setFaviconHref] = useState("/favicon.ico"); // try without state + const [t] = useTranslation(); + // console.log(t, i18n); + useEffect(() => { if (error) { error?.unAuthorized && @@ -500,7 +504,7 @@ export default function Editor({ goBack = { blank: true, requestClose: false, - text: 't("FileLocation")', + text: t("FileLocation"), url: `${combineUrl(filesUrl, `/filter?${urlFilter}`)}`, }; } diff --git a/web/ASC.Web.Editor/src/client/index.js b/web/ASC.Web.Editor/src/client/index.js index 526bb9d074..f582130ded 100644 --- a/web/ASC.Web.Editor/src/client/index.js +++ b/web/ASC.Web.Editor/src/client/index.js @@ -1,16 +1,32 @@ -import React from "react"; +import React, { Suspense } from "react"; import { hydrate } from "react-dom"; import { registerSW } from "@appserver/common/sw/helper"; import App from "../App.js"; +import { useSSR } from "react-i18next"; +import "../i18n"; const propsObj = window.__ASC_INITIAL_STATE__; +const initialI18nStore = window.initialI18nStore; +const initialLanguage = window.initialLanguage; + delete window.__ASC_INITIAL_STATE__; +delete window.initialI18nStore; +delete window.initialLanguage; const stateJS = document.getElementById("__ASC_INITIAL_STATE__"); stateJS.parentNode.removeChild(stateJS); const { props } = propsObj; -hydrate(, document.getElementById("root")); +const AppWrapper = () => { + useSSR(initialI18nStore, initialLanguage); + return ( + }> + + + ); +}; + +hydrate(, document.getElementById("root")); registerSW(); diff --git a/web/ASC.Web.Editor/src/i18n.js b/web/ASC.Web.Editor/src/i18n.js index 8e9bbf027e..30443af72d 100644 --- a/web/ASC.Web.Editor/src/i18n.js +++ b/web/ASC.Web.Editor/src/i18n.js @@ -1,6 +1,5 @@ import i18n from "i18next"; import { initReactI18next } from "react-i18next"; -import Backend from "i18next-http-backend"; import config from "../package.json"; import { LANGUAGE } from "@appserver/common/constants"; import { loadLanguagePath } from "@appserver/common/utils"; @@ -9,35 +8,32 @@ const newInstance = i18n.createInstance(); const lng = localStorage.getItem(LANGUAGE) || "en"; -newInstance - .use(initReactI18next) - .use(Backend) - .init({ - lng: lng, - fallbackLng: "en", - load: "currentOnly", - //debug: true, +newInstance.use(initReactI18next).init({ + lng: lng, + fallbackLng: "en", + load: "currentOnly", + //debug: true, - interpolation: { - escapeValue: false, // not needed for react as it escapes by default - format: function (value, format) { - if (format === "lowercase") return value.toLowerCase(); - return value; - }, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + format: function (value, format) { + if (format === "lowercase") return value.toLowerCase(); + return value; }, + }, - backend: { - loadPath: loadLanguagePath(config.homepage), - allowMultiLoading: true, - crossDomain: false, - }, + backend: { + loadPath: loadLanguagePath(config.homepage), + allowMultiLoading: true, + crossDomain: false, + }, - ns: ["Editor", "Common"], - defaultNS: "Editor", + ns: ["Editor", "Common"], + defaultNS: "Editor", - react: { - useSuspense: true, - }, - }); + react: { + useSuspense: true, + }, +}); export default newInstance; diff --git a/web/ASC.Web.Editor/src/index.js b/web/ASC.Web.Editor/src/index.js index 24a00b4b4a..c225ef1d3c 100644 --- a/web/ASC.Web.Editor/src/index.js +++ b/web/ASC.Web.Editor/src/index.js @@ -2,8 +2,38 @@ import express from "express"; import path from "path"; import template from "./server/template"; import render from "./server/render"; +import i18nextMiddleware from "i18next-express-middleware"; +import i18next from "i18next"; +import Backend from "i18next-node-fs-backend"; const app = express(); +const port = process.env.PORT || 5013; + +i18next.use(Backend).init({ + backend: { + loadPath: + path.resolve(process.cwd(), "clientBuild") + + "/locales/{{lng}}/{{ns}}.json", + allowMultiLoading: true, + crossDomain: false, + }, + fallbackLng: "en", + load: "currentOnly", + + saveMissing: true, + ns: ["Editor", "Common"], + defaultNS: "Editor", + + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + format: function (value, format) { + if (format === "lowercase") return value.toLowerCase(); + return value; + }, + }, +}); + +app.use(i18nextMiddleware.handle(i18next)); app.use( "/products/files/doceditor/static/", @@ -11,24 +41,37 @@ app.use( ); app.use(express.static(path.resolve(__dirname, "../clientBuild"))); -app.disable("x-powered-by"); - -const port = process.env.PORT || 5013; - app.get("/products/files/doceditor", async (req, res) => { const { props, content, styleTags, scriptTags } = await render(req); + const userLng = props?.props?.user?.cultureName || "en"; - const response = template( - "Server Rendered Page", - props, - content, - styleTags, - scriptTags - ); - res.setHeader("Cache-Control", "assets, max-age=604800"); - res.send(response); + i18next.changeLanguage(userLng).then(() => { + const initialLanguage = userLng; + const initialI18nStore = {}; + const usedNamespaces = req.i18n.reportNamespaces.getUsedNamespaces(); + + initialI18nStore[initialLanguage] = {}; + + usedNamespaces.forEach((namespace) => { + initialI18nStore[initialLanguage][namespace] = + req.i18n.services.resourceStore.data[initialLanguage][namespace]; + }); + + const response = template( + "Server Rendered Page", + props, + content, + styleTags, + scriptTags, + initialI18nStore, + initialLanguage + ); + res.setHeader("Cache-Control", "assets, max-age=604800"); + + res.send(response); + }); }); -//app.get("/", (req, res) => console.log("root", req)); -const isDevelopment = process.env.NODE_ENV === "development"; -app.listen(port, () => console.log(`server listen port: ${port}`)); +app.listen(port, (err) => { + console.log(`Server is listening on port ${port}`); +}); diff --git a/web/ASC.Web.Editor/src/server/render.js b/web/ASC.Web.Editor/src/server/render.js index 91e8137d14..c3ab3e75fb 100644 --- a/web/ASC.Web.Editor/src/server/render.js +++ b/web/ASC.Web.Editor/src/server/render.js @@ -5,6 +5,7 @@ import { initDocEditor } from "../helpers/utils"; import { ServerStyleSheet } from "styled-components"; import { ChunkExtractor } from "@loadable/server"; import path from "path"; +import { I18nextProvider } from "react-i18next"; const sheet = new ServerStyleSheet(); const statsFile = path.resolve("clientBuild/stats.json"); export default async (req) => { @@ -13,7 +14,13 @@ export default async (req) => { const extractor = new ChunkExtractor({ statsFile }); const scriptTags = extractor.getScriptTags(); - const content = renderToString(sheet.collectStyles()); + const content = renderToString( + sheet.collectStyles( + + + + ) + ); const styleTags = sheet.getStyleTags(); return { props, content, styleTags, scriptTags }; diff --git a/web/ASC.Web.Editor/src/server/template.js b/web/ASC.Web.Editor/src/server/template.js index 72e3fbf7ce..3234d1fd50 100644 --- a/web/ASC.Web.Editor/src/server/template.js +++ b/web/ASC.Web.Editor/src/server/template.js @@ -3,13 +3,19 @@ export default function template( initialState = {}, content = "", styleTags, - scriptTags + scriptTags, + initialI18nStore, + initialLanguage ) { const { docApiUrl } = initialState.props; const scripts = ` ${scriptTags}