Web: Doceditor: added i18n implementation on server side

This commit is contained in:
Artem Tarasov 2022-03-13 22:23:42 +03:00
parent d927c64764
commit 66411680fb
7 changed files with 119 additions and 59 deletions

View File

@ -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",
},
};

View File

@ -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}`)}`,
};
}

View File

@ -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(<App {...props} />, document.getElementById("root"));
const AppWrapper = () => {
useSSR(initialI18nStore, initialLanguage);
return (
<Suspense fallback={<div />}>
<App {...props} />
</Suspense>
);
};
hydrate(<AppWrapper />, document.getElementById("root"));
registerSW();

View File

@ -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;

View File

@ -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}`);
});

View File

@ -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(<App />));
const content = renderToString(
sheet.collectStyles(
<I18nextProvider i18n={req.i18n}>
<App />
</I18nextProvider>
)
);
const styleTags = sheet.getStyleTags();
return { props, content, styleTags, scriptTags };

View File

@ -3,13 +3,19 @@ export default function template(
initialState = {},
content = "",
styleTags,
scriptTags
scriptTags,
initialI18nStore,
initialLanguage
) {
const { docApiUrl } = initialState.props;
const scripts = `
<script id="__ASC_INITIAL_STATE__">
window.__ASC_INITIAL_STATE__ = ${JSON.stringify(initialState)}
window.initialI18nStore = JSON.parse('${JSON.stringify(
initialI18nStore
)}')
window.initialLanguage = '${initialLanguage}'
</script>
<script type='text/javascript' id='scripDocServiceAddress' src="${docApiUrl}" async></script>
${scriptTags}