feat: save language to BE

This commit is contained in:
lleohao
2024-09-10 08:52:52 +00:00
parent 637cd15ab4
commit 49900050f7
10 changed files with 48 additions and 29 deletions

View File

@ -28,7 +28,6 @@
"emoji-mart": "^5.6.0", "emoji-mart": "^5.6.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next": "^23.14.0", "i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1", "i18next-http-backend": "^2.6.1",
"jotai": "^2.9.3", "jotai": "^2.9.3",
"jotai-optics": "^0.4.0", "jotai-optics": "^0.4.0",

View File

@ -1,5 +1,10 @@
import { Group, Text, Select } from "@mantine/core"; import { Group, Text, Select } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { updateUser } from "../services/user-service";
import { IUser } from "../types/user.types";
import { useAtom } from "jotai";
import { userAtom } from "../atoms/current-user-atom";
import { useState } from "react";
export default function AccountLanguage() { export default function AccountLanguage() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -19,8 +24,17 @@ export default function AccountLanguage() {
function LanguageSwitcher() { function LanguageSwitcher() {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const [user, setUser] = useAtom(userAtom);
const [language, setLanguage] = useState(
user.settings?.preferences?.language || "en-US",
);
const handleChange = async (value: string) => {
const updatedUser = await updateUser({ language: value });
setLanguage(value);
setUser(updatedUser);
const handleChange = (value: string) => {
i18n.changeLanguage(value); i18n.changeLanguage(value);
}; };
@ -28,13 +42,14 @@ function LanguageSwitcher() {
<Select <Select
label={t("Select language")} label={t("Select language")}
data={[ data={[
{ value: "zh", label: "中文" }, { value: "en-US", label: "English (United States)" },
{ value: "en", label: "English" }, { value: "zh-CN", label: "中文 (简体)" },
]} ]}
value={i18n.language} value={language}
onChange={handleChange} onChange={handleChange}
allowDeselect={false} allowDeselect={false}
checkIconPosition="right" checkIconPosition="right"
/> />
); );
} }

View File

@ -1,6 +1,11 @@
import { IWorkspace } from "@/features/workspace/types/workspace.types"; import { IWorkspace } from "@/features/workspace/types/workspace.types";
export interface IUser { type IUserPreferences = {
fullPageWidth: boolean;
language: string;
};
export interface IUser extends IUserPreferences {
id: string; id: string;
name: string; name: string;
email: string; email: string;
@ -17,7 +22,6 @@ export interface IUser {
workspaceId: string; workspaceId: string;
deactivatedAt: Date; deactivatedAt: Date;
deletedAt: Date; deletedAt: Date;
fullPageWidth: boolean; // used for update
} }
export interface ICurrentUser { export interface ICurrentUser {
@ -26,7 +30,5 @@ export interface ICurrentUser {
} }
export interface IUserSettings { export interface IUserSettings {
preferences: { preferences: IUserPreferences
fullPageWidth: boolean;
};
} }

View File

@ -2,14 +2,19 @@ import { useAtom } from "jotai";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import useCurrentUser from "@/features/user/hooks/use-current-user"; import useCurrentUser from "@/features/user/hooks/use-current-user";
import { useTranslation } from "react-i18next";
export function UserProvider({ children }: React.PropsWithChildren) { export function UserProvider({ children }: React.PropsWithChildren) {
const [, setCurrentUser] = useAtom(currentUserAtom); const [, setCurrentUser] = useAtom(currentUserAtom);
const { data, isLoading, error } = useCurrentUser(); const { data, isLoading, error } = useCurrentUser();
const { i18n } = useTranslation();
useEffect(() => { useEffect(() => {
if (data && data.user && data.workspace) { if (data && data.user && data.workspace) {
setCurrentUser(data); setCurrentUser(data);
i18n.changeLanguage(
data.user?.settings?.preferences?.language || "en-US",
);
} }
}, [data, isLoading]); }, [data, isLoading]);

View File

@ -2,25 +2,18 @@ import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend"; import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
// don't want to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
i18n i18n
// load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) // load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
// learn more: https://github.com/i18next/i18next-http-backend // learn more: https://github.com/i18next/i18next-http-backend
// want your translations to be loaded from a professional CDN? => https://github.com/locize/react-tutorial#step-2---use-the-locize-cdn // want your translations to be loaded from a professional CDN? => https://github.com/locize/react-tutorial#step-2---use-the-locize-cdn
.use(Backend) .use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next. // pass the i18n instance to react-i18next.
.use(initReactI18next) .use(initReactI18next)
// init i18next // init i18next
// for all options read: https://www.i18next.com/overview/configuration-options // for all options read: https://www.i18next.com/overview/configuration-options
.init({ .init({
fallbackLng: "en", fallbackLng: "en-US",
debug: true, debug: true,
interpolation: { interpolation: {

View File

@ -12,4 +12,8 @@ export class UpdateUserDto extends PartialType(
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
fullPageWidth: boolean; fullPageWidth: boolean;
@IsOptional()
@IsString()
language: string;
} }

View File

@ -33,6 +33,13 @@ export class UserService {
); );
} }
if (typeof updateUserDto.language !== 'undefined') {
return this.updateUserLanguagePreference(
userId,
updateUserDto.language,
);
}
if (updateUserDto.name) { if (updateUserDto.name) {
user.name = updateUserDto.name; user.name = updateUserDto.name;
} }
@ -59,4 +66,8 @@ export class UserService {
fullPageWidth, fullPageWidth,
); );
} }
async updateUserLanguagePreference(userId: string, language: string) {
return this.userRepo.updatePreference(userId, 'language', language);
}
} }

12
pnpm-lock.yaml generated
View File

@ -231,9 +231,6 @@ importers:
i18next: i18next:
specifier: ^23.14.0 specifier: ^23.14.0
version: 23.14.0 version: 23.14.0
i18next-browser-languagedetector:
specifier: ^8.0.0
version: 8.0.0
i18next-http-backend: i18next-http-backend:
specifier: ^2.6.1 specifier: ^2.6.1
version: 2.6.1 version: 2.6.1
@ -5468,9 +5465,6 @@ packages:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
i18next-browser-languagedetector@8.0.0:
resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==}
i18next-http-backend@2.6.1: i18next-http-backend@2.6.1:
resolution: {integrity: sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==} resolution: {integrity: sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==}
@ -13961,10 +13955,6 @@ snapshots:
human-signals@2.1.0: {} human-signals@2.1.0: {}
i18next-browser-languagedetector@8.0.0:
dependencies:
'@babel/runtime': 7.23.7
i18next-http-backend@2.6.1: i18next-http-backend@2.6.1:
dependencies: dependencies:
cross-fetch: 4.0.0 cross-fetch: 4.0.0
@ -16335,7 +16325,7 @@ snapshots:
terser@5.29.2: terser@5.29.2:
dependencies: dependencies:
'@jridgewell/source-map': 0.3.6 '@jridgewell/source-map': 0.3.6
acorn: 8.11.3 acorn: 8.12.1
commander: 2.20.3 commander: 2.20.3
source-map-support: 0.5.21 source-map-support: 0.5.21