Я довольно долго пытался заставить SSR работать с react-i18next
, документации несколько не хватает, поэтому я собрал все, что мог, из некоторых других репозиториев и их пример razzle-ssr.
У меня работает серверная часть, где я:
- Настройте экспресс, вызовите соответствующее промежуточное ПО, получите соответствующий языковой стандарт:
const app = express();
await i18n
.use(Backend)
.use(i18nextMiddleware.LanguageDetector)
.init(options)
app.use(i18nextMiddleware.handle(i18n));
app.use('/locales', express.static(`${appDirectory}/locales`));
- Получите DOM-представление приложения по запросу:
app.get('/*', req => {
//...
const html = ReactDOMServer.renderToString(
<I18nextProvider i18n={req.i18n}>>
<App />
</I18nextProvider>
)
// ...
})
- Добавьте
initialI18nStore
к содержанию запроса:
const initialI18nStore = {};
req.i18n.languages.forEach(l => {
initialI18nStore[l] = req.i18n.services.resourceStore.data[l];
});
const initialLanguage = req.i18n.language;
content = content.replace(
/<head>/,
`<head>
<script>
window.initialI18nStore = "${JSON.stringify(initialI18nStore)}";
window.initialLanguage = "${initialLanguage.slice(0, 2)}";
</script>`,
);
Это отлично работает, когда я curl http://localhost:3000/
получаю правильный DOM с загруженными / замененными переводами.
Проблема, с которой я сталкиваюсь, - это гидратация.
Я пробовал использовать useSSR
с Suspense
, но у меня ничего не получалось. Но я чувствую, что в основе этого возникнет та же проблема: i18n
необходимо инициализировать языками, прежде чем мы должны гидратировать приложение. Верный(?)
Я попытался сымитировать то же, что и useSSR
, дождавшись инициализации экземпляра client
i18n
перед гидратированием приложения:
// client i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(initReactI18next)
.use(Backend)
.use(LanguageDetector);
const i18nInit = () => {
return new Promise(resolve => {
// @todo: shim in moment locale here
i18n.init(options, () => resolve(i18n));
});
};
export default i18nInit;
// client index.js
const renderApp = async () => {
let i18n = await i18ninit();
if (window.initialI18nStore) {
i18n.services.resourceStore.data = window.initialI18nStore;
}
hydrate(<BaseApp />, document.getElementById('root'));
};
renderApp();
Проблема заключается в следующем: приложение отлично отображает представление DOM, предоставленное сервером. Затем, когда я жду, пока экземпляр клиента i18n
инициализируется, а затем увлажняет приложение, я получаю огромное мерцание без стиля, а затем оно возвращает то же представление, что и представление DOM.
Я также попытался сделать отложенный рендеринг внутри функционального компонента:
const BaseApp = () => {
const [render, setRender] = useState(false);
useEffect( () => {
await initI18();
i18n.services.resourceStore.data = INITIALI18NSTORE;
setRender(true);
}, [])
if(!render) return null;
return <App />
}
Но это вызывает аналогичное, но вместо мерцания без стиля, белый экран из-за возврата null
.
Есть ли что-то, что мне здесь не хватает? Или я что-то не в порядке? Как получить плавный переход от стилей DOM +, предоставленных сервером, к стилям, предоставленным моим клиентом, с включенными переводами?