Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Language Localization #473

Open
wants to merge 51 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0297ae2
add deps
Xavier-Charles Nov 19, 2024
4fdc7bf
hide overflow of screen
Xavier-Charles Nov 19, 2024
cbb0e4c
set up japanese translation
Xavier-Charles Nov 19, 2024
28e3316
make all navigation text translateable
Xavier-Charles Nov 20, 2024
84b3d7c
news and portfolio translation
Xavier-Charles Nov 20, 2024
264825a
add tutorials transcription
Xavier-Charles Nov 20, 2024
cf0ac7d
add more nav translations
Xavier-Charles Nov 20, 2024
bb57837
add podcast and video translations
Xavier-Charles Nov 20, 2024
24ef967
translate more items
Xavier-Charles Nov 22, 2024
91704ea
add translation for global messages
Xavier-Charles Nov 22, 2024
08405ed
add translation verasity widget
Xavier-Charles Nov 22, 2024
2a7f5ad
add cookies translate
Xavier-Charles Nov 22, 2024
2467503
countdown mudule
Xavier-Charles Nov 22, 2024
b9f8348
update date
Xavier-Charles Nov 22, 2024
a21ee0d
custom data module
Xavier-Charles Nov 22, 2024
4d92ddb
add error page
Xavier-Charles Nov 22, 2024
ea72e7e
gas
Xavier-Charles Nov 22, 2024
e705efa
market
Xavier-Charles Nov 22, 2024
ba7c839
qna
Xavier-Charles Nov 22, 2024
b35d195
summary
Xavier-Charles Nov 22, 2024
37f4aeb
video
Xavier-Charles Nov 22, 2024
53b8f01
translate about us
Xavier-Charles Nov 22, 2024
ea47a8b
calendar categories
Xavier-Charles Nov 22, 2024
8723f0a
error page
Xavier-Charles Nov 22, 2024
478c154
wallet_view
Xavier-Charles Nov 22, 2024
3963194
terns of service
Xavier-Charles Nov 22, 2024
f7fbcc3
add mroe translations
Xavier-Charles Nov 25, 2024
9620b8c
add migration
Xavier-Charles Nov 25, 2024
387822f
clean up trnsl
Xavier-Charles Nov 25, 2024
ddfa379
Add language switching
Xavier-Charles Nov 25, 2024
9af64ca
clean up
Xavier-Charles Nov 25, 2024
0193b49
clean up
Xavier-Charles Nov 25, 2024
28ed483
add spanish and french
Xavier-Charles Nov 27, 2024
53fd114
update date locale
Xavier-Charles Nov 27, 2024
9e092b7
date time translation
Xavier-Charles Nov 27, 2024
ad4b5fc
update england flag
Xavier-Charles Dec 2, 2024
b84f173
add turkish
Xavier-Charles Dec 3, 2024
7a209c8
add language button to menu
Xavier-Charles Dec 3, 2024
ea71ddb
update yarn
Xavier-Charles Dec 23, 2024
84d5899
reload on lang change
Xavier-Charles Dec 27, 2024
619fc26
remove unnecessary code
Xavier-Charles Dec 27, 2024
9784913
make news nav items update on lang change
Xavier-Charles Jan 13, 2025
4251e15
make sort items update on lang change
Xavier-Charles Jan 13, 2025
8da1b8f
make translations change on language change
Xavier-Charles Jan 13, 2025
fbbf1cc
refetch queries on lang change
Xavier-Charles Jan 13, 2025
a32cb99
fix unique key issue
Xavier-Charles Jan 13, 2025
cf01038
refactor dateUtils
Xavier-Charles Jan 13, 2025
691240b
update raw calls of i18next to use react patterns
Xavier-Charles Jan 13, 2025
69243d0
add language header
Xavier-Charles Jan 13, 2025
4df7325
update date
Xavier-Charles Jan 14, 2025
533d2b3
key lettering case update
Xavier-Charles Jan 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,19 @@
"es6-error": "4.1.1",
"firebase": "10.0.0",
"html2canvas": "1.4.1",
"i18next": "23.16.5",
"i18next-browser-languagedetector": "8.0.0",
"ionicons": "7.3.0",
"md5": "2.3.0",
"moment": "2.30.1",
"moment-with-locales-es6": "^1.0.1",
"query-string": "9.1.1",
"react": "18.2.0",
"react-beautiful-dnd": "13.1.1",
"react-dom": "18.2.0",
"react-ga4": "2.1.0",
"react-hot-toast": "2.4.1",
"react-i18next": "15.1.1",
"react-laag": "2.0.5",
"react-markdown": "8.0.7",
"react-microsoft-clarity": "1.2.0",
Expand All @@ -62,6 +66,7 @@
"react-use-audio-player": "AlphadayHQ/useAudioPlayer",
"redux-persist": "6.0.0",
"remark-breaks": "3.0.3",
"sharp": "0.32.6",
"unist-util-visit": "5.0.0",
"uuid": "9.0.0",
"viem": "1.2.5",
Expand Down Expand Up @@ -98,4 +103,4 @@
"vite-plugin-pwa": "0.19.2",
"vite-plugin-svgr": "3.2.0"
}
}
}
2 changes: 2 additions & 0 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
useViewRoute,
useGaTracker,
} from "./api/hooks";
import { usePreferredLanguage } from "./api/hooks/usePreferredLanguage";
import { useGetRemoteStatusQuery } from "./api/services";
import { useAppDispatch } from "./api/store/hooks";
import walletConnectProvider from "./api/store/providers/wallet-connect-provider";
Expand All @@ -31,6 +32,7 @@ const goToLandingPage = () => {

const AppRoutes = () => {
useGaTracker();
usePreferredLanguage();

const dispatch = useAppDispatch();
const { error } = useGetRemoteStatusQuery(undefined, {
Expand Down
37 changes: 37 additions & 0 deletions packages/frontend/src/api/hooks/usePreferredLanguage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useEffect, useRef } from "react";
import i18next from "i18next";
import moment from "moment-with-locales-es6";
import { useDispatch } from "react-redux";
import { alphadayApi } from "../services";
import { useAppSelector } from "../store/hooks";
import { Logger } from "../utils/logging";

export const usePreferredLanguage = () => {
const dispatch = useDispatch();
const selectedLangCode = useAppSelector(
(state) => state.ui.selectedLanguageCode
);
const prevLangCodeRef = useRef(selectedLangCode);

useEffect(() => {
if (!selectedLangCode || i18next.language === selectedLangCode) {
return;
}
moment.locale(selectedLangCode);
i18next
.changeLanguage(selectedLangCode)
.then(() => {
if (selectedLangCode !== prevLangCodeRef.current) {
prevLangCodeRef.current = selectedLangCode;
// Reset all queries
dispatch(alphadayApi.util.resetApiState());
}
})
.catch((e) => {
Logger.error(
"usePreferredLanguage::Error changing language::",
e
);
});
}, [selectedLangCode, dispatch]);
};
19 changes: 16 additions & 3 deletions packages/frontend/src/api/hooks/useTutorial.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback } from "react";
import { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { setCurrentTutorialTip, setStoreShowTutorial } from "src/api/store";
import { useAppSelector, useAppDispatch } from "src/api/store/hooks";
import { tutorials } from "src/containers/tutorial/staticData";
Expand All @@ -20,16 +21,28 @@ interface ITutorial {
}

export const useTutorial: () => ITutorial = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { tutFocusElemRef, setTutFocusElemRef } = useTutorialContext();
const { isFullSize } = useViewRoute();
const { enabled: isWalletBoardAllowed } = useFeatureFlags(
EFeaturesRegistry.WalletBoard
);

const translate = useCallback(
(key: string) => {
return t(`tutorials.${key}`);
},
[t]
);
const translatedTutorials = useMemo(
() => tutorials(translate),
[translate]
);

const allowedTutorials = isWalletBoardAllowed
? tutorials
: tutorials.filter(
? translatedTutorials
: translatedTutorials.filter(
(tutorial) => tutorial.id !== ETutorialTipId.WalletView
);

Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/src/api/services/alphadayApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import i18next from "i18next";
import { TUserAuth } from "src/api/types";
import { Logger } from "src/api/utils/logging";
import CONFIG from "../../config/config";
Expand All @@ -22,6 +23,7 @@ export const alphadayApi = createApi({
headers.set("Version", CONFIG.APP.VERSION);
headers.set("X-App-Id", CONFIG.APP.X_APP_ID);
headers.set("X-App-Secret", CONFIG.APP.X_APP_SECRET);
headers.set("Accept-Language", i18next.language);
// @ts-expect-error
const authState = getState().user.auth as TUserAuth | null;
if (authState != null && authState?.token !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/api/services/tvl/tvlEndpoints.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import moment from "moment";
import moment from "moment-with-locales-es6";
import queryString from "query-string";
import { TProjectTvlHistory, TTvlHistory } from "src/api/types";
import { isEmptyObj } from "src/api/utils/helpers";
Expand Down
21 changes: 20 additions & 1 deletion packages/frontend/src/api/store/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TUserView,
WalletConnectionState,
} from "../types";
import { EnumLanguageCode } from "../types/language";
import { Logger } from "../utils/logging";
import { RootState } from "./reducer";
import { IUIState } from "./slices/ui";
Expand Down Expand Up @@ -38,7 +39,13 @@ type PersistedRootState = (PersistedState & RootState) | undefined;
* 102: (s: RootStateV101) => PersistedRootState
*/

type RootStateV106 = PersistedRootState;
type RootStateV107 = PersistedRootState;
type RootStateV106 =
| (PersistedState &
Omit<RootState, "ui"> & {
ui: Omit<IUIState, "showLanguageModal" | "selectedLanguageCode">;
})
| undefined;

type RootStateV105 =
| (PersistedState &
Expand Down Expand Up @@ -111,6 +118,7 @@ type TMigrations = MigrationManifest & {
104: (s: RootStateV103) => RootStateV104;
105: (s: RootStateV104) => RootStateV105;
106: (s: RootStateV105) => RootStateV106;
107: (s: RootStateV106) => RootStateV107;
};

/**
Expand Down Expand Up @@ -280,6 +288,17 @@ const migrations: TMigrations = {
},
};
},
107: (s: RootStateV106): RootStateV107 => {
if (!s) return undefined;
return {
...s,
ui: {
...s.ui,
selectedLanguageCode: EnumLanguageCode.EN,
showLanguageModal: false,
},
};
},
};

export default migrations;
19 changes: 19 additions & 0 deletions packages/frontend/src/api/store/slices/ui.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { ECookieChoice, TTutorialTip } from "src/api/types";
import { EnumLanguageCode } from "src/api/types/language";

export type TTheme = "dark";
export interface ITutorialState {
Expand All @@ -11,6 +12,8 @@ export interface IUIState {
showWidgetLib: boolean;
showBalance: boolean;
showAboutModal: boolean;
showLanguageModal: boolean;
selectedLanguageCode: EnumLanguageCode;
tutorial: ITutorialState;
cookieChoice: ECookieChoice | undefined;
mobile: {
Expand All @@ -26,6 +29,8 @@ const initialState: IUIState = {
theme: "dark",
showWidgetLib: false,
showAboutModal: false,
showLanguageModal: false,
selectedLanguageCode: EnumLanguageCode.EN,
showBalance: true,
tutorial: { showTutorial: undefined, currentTutorialTip: undefined },
cookieChoice: undefined,
Expand Down Expand Up @@ -58,9 +63,21 @@ const uiSlice = createSlice({
toggleAboutModal(draft) {
draft.showAboutModal = !draft.showAboutModal;
},
toggleLanguageModal(draft) {
draft.showLanguageModal = !draft.showLanguageModal;
},
toggleWidgetsNavOpen(draft) {
draft.mobile.widgetsNavOpen = !draft.mobile.widgetsNavOpen;
},
setSelectedLanguageCode(
draft,
action: PayloadAction<{ code: EnumLanguageCode }>
) {
const {
payload: { code },
} = action;
draft.selectedLanguageCode = code;
},
setStoreShowTutorial(draft, action: PayloadAction<{ show: boolean }>) {
const {
payload: { show },
Expand Down Expand Up @@ -105,7 +122,9 @@ export const {
toggleShowWidgetLib,
toggleShowBalance,
toggleAboutModal,
toggleLanguageModal,
toggleWidgetsNavOpen,
setSelectedLanguageCode,
setStoreShowTutorial,
setCurrentTutorialTip,
setCookieChoice,
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/api/store/slices/views.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "src/mocks/libraryMocks";
import moment from "moment";
import moment from "moment-with-locales-es6";
import { ETag, TCachedView, TSubscribedView } from "src/api/types";
import { customTableModuleDataMock } from "src/mocks/tables";
import {
Expand Down
7 changes: 7 additions & 0 deletions packages/frontend/src/api/types/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum EnumLanguageCode {
EN = "en",
FR = "fr",
ES = "es",
JA = "ja",
TR = "tr",
}
18 changes: 9 additions & 9 deletions packages/frontend/src/api/types/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ export type TCoinMarketHistory = {
};

export const CHART_RANGE_OPTIONS = {
oneDay: "1D",
oneWeek: "1W",
oneMonth: "1M",
threeMonths: "3M",
yearToDate: "YTD",
oneYear: "1Y",
threeYear: "3Y",
all: "ALL",
oneDay: { value: "1D", translationKey: `datelocale.d`, prefix: "1" },
oneWeek: { value: "1W", translationKey: `datelocale.w`, prefix: "1" },
oneMonth: { value: "1M", translationKey: `datelocale.M`, prefix: "1" },
threeMonths: { value: "3M", translationKey: `datelocale.M`, prefix: "3" },
yearToDate: { value: "YTD", translationKey: "datelocale.ytd", prefix: "" },
oneYear: { value: "1Y", translationKey: `datelocale.y`, prefix: "1" },
threeYear: { value: "3Y", translationKey: `datelocale.y`, prefix: "3" },
all: { value: "ALL", translationKey: "navigation.general.all", prefix: "" },
} as const;
type TRangeKeys = keyof typeof CHART_RANGE_OPTIONS;
export type TChartRange = (typeof CHART_RANGE_OPTIONS)[TRangeKeys];
export type TChartRange = (typeof CHART_RANGE_OPTIONS)[TRangeKeys]["value"];
2 changes: 1 addition & 1 deletion packages/frontend/src/api/utils/calendarUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import chroma from "chroma-js";
import moment from "moment";
import moment from "moment-with-locales-es6";
import { TRemoteEvent, TRemoteEventDetails } from "src/api/services";
import { TEvent, TEventDetails } from "src/api/types";
import {
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/api/utils/customDataUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import moment from "moment";
import moment from "moment-with-locales-es6";
import ReactMarkdown from "react-markdown";
import remarkBreaks from "remark-breaks";
import {
Expand Down
32 changes: 17 additions & 15 deletions packages/frontend/src/api/utils/dateUtils.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import moment from "moment";
import i18next from "i18next";
import moment from "moment-with-locales-es6";
/**
* returns a text that represents the given datetime
* relative to now.
* @param date | date-string
* @returns string
*/

export const computeDuration = (date: string | Date): string => {
moment.updateLocale("en", {
moment.updateLocale(i18next.language, {
relativeTime: {
future: "in %s", // this shouldn't occur If it does there's an error.
past: "%s ago",
s: "%ds",
m: "%dm",
mm: "%dm",
h: "%dh",
hh: "%dh",
d: "%dd",
dd: "%dd",
M: "%dmo",
MM: "%dmo",
y: "%dy",
yy: "%dy",
future: `${i18next.t("datelocale.future")}`, // this shouldn't occur If it does there's an error.
past: `${i18next.t("datelocale.past")}`,
s: `%d${i18next.t("datelocale.s")}`,
m: `%d${i18next.t("datelocale.m")}`,
mm: `%d${i18next.t("datelocale.mm")}`,
h: `%d${i18next.t("datelocale.h")}`,
hh: `%d${i18next.t("datelocale.hh")}`,
d: `%d${i18next.t("datelocale.d")}`,
dd: `%d${i18next.t("datelocale.dd")}`,
M: `%d${i18next.t("datelocale.M")}`,
MM: `%d${i18next.t("datelocale.MM")}`,
y: `%d${i18next.t("datelocale.y")}`,
yy: `%d${i18next.t("datelocale.yy")}`,
},
});

Expand Down
20 changes: 7 additions & 13 deletions packages/frontend/src/api/utils/sortOptions.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import { EItemsSortBy } from "../services";
import { translateLabels } from "./translationUtils";

const SORT_OPTIONS: {
label: string;
value: EItemsSortBy;
}[] = [
export const generateSortOptions = () => [
{
label: "(A-Z)",
value: EItemsSortBy.Name,
label: translateLabels("(A-Z)"),
},
{
label: "Popular",
value: EItemsSortBy.Popular,
label: translateLabels("Popular"),
},
{
label: "New",
value: EItemsSortBy.New,
label: translateLabels("New"),
},
];

export const getSortOptionsArray = (): string[] => {
return SORT_OPTIONS.map((option) => option.label);
};

export const getSortOptionValue = (label: string): EItemsSortBy | null => {
const option = SORT_OPTIONS.find((op) => op.label === label);
export const getSortOptionValue = (value: string): EItemsSortBy | null => {
const option = generateSortOptions().find((op) => op.value === value);
if (!option) {
return null;
}
Expand Down
Loading