Перевод состоит из двух частей:
Сама страница, текст, которой приходит с бэка по REST API запросу с указанием нужного языка
Перевод вспомогательных компонентов: кнопки, элементы навигации, отдельные заголовки.
Так же нам нужно переключение с английского языка на французский и обратно.
Для удобства работы будем использовать библиотеку Axios (установка npm i axios).
В корне проекта в папке store заводим инстанс, который отвечает за первичную настройку запросов:
apiInstance.ts
import axios from 'axios';
const URL = process.env.NEXT_PUBLIC_API_URL;
const instance = axios.create({
baseURL: `${URL}/api/`,
});
export default instance;
Для безопасности разработки используем process.env для хранения URL бэкэнда.
В той же папке создаём файлы с GET запросом для отдельных страниц с нужными эндпоинтами. Для примера назовём один из файлов наших страниц apiTeam.ts, а компонент отрисовки этой страницы Team. Такие файлы мы будем заводить для каждой страницы, где предусмотрен перевод.
apiTeam.ts
import instance from './apiInstance';
const getTeam = async (lang = 'Eng') => {
const res = await instance.get('team', {
headers: {
Language: lang,
},
});
return res;
};
export default getTeam;
Далее заводим файл apiTranslate.ts с GET запросом для перевода вспомогательных компонентов, которые мы будем использовать на всех страницах, где есть вспомогательные компоненты с переводом:
apiTranslate.ts
import instance from './apiInstance';
const getTranslateInfo = async (lang: string) => {
const res = await instance.get('translate/', {
headers: {
Language: lang,
},
});
return res;
};
export default getTranslateInfo;
Делаем в компонент Team импорт созданного запроса.
import getTeam from '../../store/apiTeam';
и теперь можем использовать для отрисовки страницы
useEffect(() => {
setIsLoading(true);
getTeam(lang = 'Eng")
.then((res) => setData(res.data))
.catch((err) => console.log(err))
.finally(() => setIsLoading(false));
}, []);
используем стейт переменную const [isLoading, setIsLoading] = useState(true), чтобы страница не была пустой при отрисовке.
{isLoading ? (
<Load />
) : (
<Team>
)}
В getTeam(lang = 'Eng") нам нужно иметь актуальное состояние языка. Пока по умолчанию у нас 'Eng'. Для настройки хранилища и выполнения операций по переключению языка будем использовать Redux Toolkit. В уже знакомой нам папке store заводим файл store.ts:
import { configureStore } from '@reduxjs/toolkit';
import reduserLang from './languageSlics';
export const store = () => {
return configureStore({
reducer: {
reduserLang,
},
});
};
export type AppStore = ReturnType<typeof store>;
export type RootState = returnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];
в нем мы делаем первичную настройку хранилища. Сами стейт переменные и экшены мы настраиваем в файле languageSlics.ts:
import { createSlice } from '@reduxjs/toolkit';
interface LanguageState {
lang: string;
}
const language = createSlice({
name: 'isLang',
initialState: {
lang: 'Eng'
} as LanguageState,
reducers: {
setLang(state: LanguageState, payload: { payload: string }) {
state.lang = payload.payload;
}
},
});
export default language.reducer;
export const { setLang } = language.actions;
В результате у нас есть стейт переменная lang, которая храниться в redux хранилище, и экшен setLang, который меняет значение стейт переменной lang.
В компонент Team мы можем импортировать import { useSelector } from 'react-redux' и использовать конструкцию:
const lang = useSelector(
(state: { reduserLang: { lang: any } }) => state.reduserLang.lang
);
так мы получаем доступ к актуальному значению языка и можем его использовать на странице для запроса в бэкэнд:
getTeam(lang)
.then((res) => setData(res.data))
.catch((err) => console.log(err))
Так же у нас остаётся вопрос перевода вспомогательных компонентов. Действуем аналогично. В файле languageSlics.ts заводим переменную words и экшен setWords для неё:
import { createSlice } from '@reduxjs/toolkit';
interface LanguageState {
lang: string;
words: {}[];
}
const language = createSlice({
name: 'isLang',
initialState: {
lang: 'Eng',
words: [],
} as LanguageState,
reducers: {
setLang(state: LanguageState, payload: { payload: string }) {
state.lang = payload.payload;
},
setWords(state: LanguageState, payload: { payload: [] }) {
state.words = payload.payload;
},
},
});
export default language.reducer;
export const { setLang, setWords } = language.actions;
Теперь у нас в хранилище 2 переменных: lang: 'Eng', words: [] и экшены для изменения их значения setLang, setWords. Как говорилось выше, в lang будет храниться актуальный язык для запроса к бэкэнду. а мы будем хранить массив объектов со значением текста кнопок, заголовка и прочего. Пример массива:
["nWho": "Who we helped",
"nUrgent": "Urgent help",
"nNews": "News",
"nAbout": "About us",
"nContacts": "Contacts",
"nReports": "Reports and documentation",
"nTeam": "Team",
"bDonate": "Donate now",
"bLearn": "Learn more",
"bShare": "Share",
"bCopied": "Copied",
"bSee": "See all",
"bBack": "Back",
"bHome": "Home",
"bLoad": "Load more",
"hAbout": "About us",
"hUrgent": "Urgent help",
"hNews": "News",
"hPartners": "Partners",
"hSubscribe": "Subscribe to our newsletter",
"hPlaceholder": "Your email address",
"fPolicy": "Privacy Policy",
"fSite": "Site development",
"fCollected": "Funds collected",
"sReport": "Report"]
Переменную words мы можем использовать на странице, предварительно импортировав хук import { useSelector } from 'react-redux' и получить доступ к переменной через:
const words = useSelector(
(state: { reduserLang: { words: any } }) => state.reduserLang.words
);
Но для начала нам нужно сделать запрос к бэкэнду с языком для получения значений слов и поместить эти значения в стейт переменную words :
getTranslate(lang)
.then((res) => {
dispatch(setWords(res.data));
})
.catch((err) => console.log(err))
Не забываем получить доступ к стейт переменной lang:
const lang = useSelector(
(state: { reduserLang: { lang: any } }) => state.reduserLang.lang
);
Таким образом, мы имеем значение языка по умолчанию и набор слов, которые мы можем использовать для перевода вспомогательных компонентов.
Остаётся сделать переключение языка. Так как у нас в проекте переключение языка было в header, то и функционал переключения языка тоже поместил в header:
const toggleLang = () => {
const langCurrent = lang === 'Eng' ? 'Fr' : 'Eng';
dispatch(setLang(langCurrent));
};
так же там поместил функционал для заполнения переменной words:
useEffect(() => {
getTranslate(lang)
.then((res) => {
dispatch(setWords(res.data));
})
.catch((err) => console.log(err))
}, []);
И не забываем отслеживать переключение языка:
useEffect(() => {
getTranslate(lang)
.then((res) => {
dispatch(setWords(res.data[0]));
})
.catch((err) => console.log(err))
}, [lang]);
Но тут появляется тонкий момент, всё хорошо работает до того, как пользователь не обновит страницу. Переменные lang сбрасывается до начального значения и язык снова становиться по умолчанию - английским. Что бы этого избежать, мы добавим хранение актуального значения lang в sessionStorage. Для этого доработаем в функцию переключения языка
sessionStorage.setItem('lang', langCurrent):
const toggleLang = () => {
const langCurrent = lang === 'Eng' ? 'Fr' : 'Eng';
sessionStorage.setItem('lang', langCurrent);
dispatch(setLang(langCurrent));
};
И перед GET запросом проверяем, если есть значение в sessionStorage по ключу lang, если есть, то берём это значение, если нет, то берём значение по умолчанию:
const langCurrent = sessionStorage.getItem('lang')
? sessionStorage.getItem('lang')
: lang;
Так же актуализируем состояние языка в стейт переменной lang и sessionStorage:
sessionStorage.getItem('lang')
? dispatch(setLang(sessionStorage.getItem('lang')))
: sessionStorage.setItem('lang', lang);
Соберём запрос:
const langCurrent = sessionStorage.getItem('lang')
? sessionStorage.getItem('lang')
: lang;
sessionStorage.getItem('lang')
? dispatch(setLang(sessionStorage.getItem('lang')))
: sessionStorage.setItem('lang', lang);
getTranslate(langCurrent)
.then((res) => {
dispatch(setWords(res.data[0]));
})
.catch((err) => console.log(err));
Если sessionStorage.getItem('lang') пусто, то мы берём значение по умолчанию из lang и его записываем в sessionStorage.setItem('lang', lang).Если запись в sessionStorage присутствует по ключу lang, то используем это значение. Таким образом, при перезагрузке страницы, у нас остаётся актуальное значение языка.