-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Implement Internationalization (en and fr) for Multi-language Support #485
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
Changes from 12 commits
f1c61e6
7560e16
62ccfdb
6dae8b1
ec5e1f0
c67daa9
20498ec
778d307
2b0a5dd
bb3e449
6f25a0a
ca3e01c
ecd5c9a
3418b9a
b2300c7
4194541
71cd197
9e09bd1
318a58e
63fa13a
a816afb
6b4b98d
85917a1
f284eb1
d8e4b76
a8ec4b0
d84767e
feb18b4
da1ff45
26c238c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -203,4 +203,19 @@ | |
|
|
||
| response.status(200).send(); | ||
| } | ||
|
|
||
| @Post('/changeLanguage') | ||
| changeLanguage(@Body('language') language: string, @Res({ passthrough: true }) response: Response) { | ||
| const maxAge = 1000 * 60 * 60 * 24 * 365 * 10; // 10 years in milliseconds | ||
| response.cookie('NEXT_LOCALE', language, { | ||
| domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!), | ||
|
||
| secure: true, | ||
| httpOnly: true, | ||
| maxAge: maxAge, | ||
| expires: new Date(Date.now() + maxAge), | ||
| sameSite: 'none', | ||
| }); | ||
|
|
||
| response.status(200).send(); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add input validation and error handling for language selection. The language parameter should be validated to ensure it matches supported locales. Additionally, error handling should be implemented. + import { IsIn } from 'class-validator';
+
+ class ChangeLanguageDto {
+ @IsIn(['en', 'fr'])
+ language: string;
+ }
@Post('/changeLanguage')
- changeLanguage(@Body('language') language: string, @Res({ passthrough: true }) response: Response) {
+ async changeLanguage(@Body() { language }: ChangeLanguageDto, @Res({ passthrough: true }) response: Response) {
const maxAge = 1000 * 60 * 60 * 24 * 365 * 10; // 10 years in milliseconds
+ try {
response.cookie('NEXT_LOCALE', language, {
domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
secure: true,
httpOnly: true,
maxAge: maxAge,
expires: new Date(Date.now() + maxAge),
sameSite: 'none',
});
response.status(200).send();
+ } catch (error) {
+ throw new HttpException('Failed to set language preference', 500);
+ }
}
🧰 Tools🪛 GitHub Check: ESLint[warning] 211-211: Disallow non-null assertions using the |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,14 @@ | ||
| import {Metadata} from "next"; | ||
| import { useTranslations } from "next-intl"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: 'Error', | ||
| description: '', | ||
| } | ||
|
|
||
| export default async function Page() { | ||
| const t= useTranslations('Error') | ||
| return ( | ||
| <div>We are experiencing some difficulty, try to refresh the page</div> | ||
| <div>{t("We are experiencing some difficulty, try to refresh the page")}</div> | ||
|
||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,8 @@ | |||||||||||||||||||||||||||||||||||||||||||
| import { Fragment } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { PHProvider } from '@gitroom/react/helpers/posthog'; | ||||||||||||||||||||||||||||||||||||||||||||
| import UtmSaver from '@gitroom/helpers/utils/utm.saver'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { NextIntlClientProvider } from 'next-intl'; | ||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / ESLint Disallow unused variables Warning
'NextIntlClientProvider' is defined but never used.
|
||||||||||||||||||||||||||||||||||||||||||||
| import { getLocale, getMessages } from 'next-intl/server'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { ToltScript } from '@gitroom/frontend/components/layout/tolt.script'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] }); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -21,9 +23,10 @@ | |||||||||||||||||||||||||||||||||||||||||||
| const Plausible = !!process.env.STRIPE_PUBLISHABLE_KEY | ||||||||||||||||||||||||||||||||||||||||||||
| ? PlausibleProvider | ||||||||||||||||||||||||||||||||||||||||||||
| : Fragment; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const locale = await getLocale(); | ||||||||||||||||||||||||||||||||||||||||||||
| const messages = await getMessages(); | ||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / ESLint Disallow unused variables Warning
'messages' is assigned a value but never used.
|
||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||
| <html className={interClass}> | ||||||||||||||||||||||||||||||||||||||||||||
| <html className={interClass} lang={locale}> | ||||||||||||||||||||||||||||||||||||||||||||
| <head> | ||||||||||||||||||||||||||||||||||||||||||||
| <link | ||||||||||||||||||||||||||||||||||||||||||||
| rel="icon" | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -32,32 +35,33 @@ | |||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||
| </head> | ||||||||||||||||||||||||||||||||||||||||||||
| <body className={clsx(chakra.className, 'text-primary dark')}> | ||||||||||||||||||||||||||||||||||||||||||||
| <VariableContextComponent | ||||||||||||||||||||||||||||||||||||||||||||
| storageProvider={ | ||||||||||||||||||||||||||||||||||||||||||||
| process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare' | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL!} | ||||||||||||||||||||||||||||||||||||||||||||
| plontoKey={process.env.NEXT_PUBLIC_POLOTNO!} | ||||||||||||||||||||||||||||||||||||||||||||
| billingEnabled={!!process.env.STRIPE_PUBLISHABLE_KEY} | ||||||||||||||||||||||||||||||||||||||||||||
| discordUrl={process.env.NEXT_PUBLIC_DISCORD_SUPPORT!} | ||||||||||||||||||||||||||||||||||||||||||||
| frontEndUrl={process.env.FRONTEND_URL!} | ||||||||||||||||||||||||||||||||||||||||||||
| isGeneral={!!process.env.IS_GENERAL} | ||||||||||||||||||||||||||||||||||||||||||||
| uploadDirectory={process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY!} | ||||||||||||||||||||||||||||||||||||||||||||
| tolt={process.env.NEXT_PUBLIC_TOLT!} | ||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||
| <ToltScript /> | ||||||||||||||||||||||||||||||||||||||||||||
| <Plausible | ||||||||||||||||||||||||||||||||||||||||||||
| domain={!!process.env.IS_GENERAL ? 'postiz.com' : 'gitroom.com'} | ||||||||||||||||||||||||||||||||||||||||||||
| <NextIntlClientProvider messages={messages}> | ||||||||||||||||||||||||||||||||||||||||||||
| {/* @ts-ignore */} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| <VariableContextComponent | ||||||||||||||||||||||||||||||||||||||||||||
| storageProvider={ | ||||||||||||||||||||||||||||||||||||||||||||
| process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare' | ||||||||||||||||||||||||||||||||||||||||||||
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show fixed
Hide fixed
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL!} | ||||||||||||||||||||||||||||||||||||||||||||
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show fixed
Hide fixed
|
||||||||||||||||||||||||||||||||||||||||||||
| plontoKey={process.env.NEXT_PUBLIC_POLOTNO!} | ||||||||||||||||||||||||||||||||||||||||||||
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show fixed
Hide fixed
|
||||||||||||||||||||||||||||||||||||||||||||
| billingEnabled={!!process.env.STRIPE_PUBLISHABLE_KEY} | ||||||||||||||||||||||||||||||||||||||||||||
| discordUrl={process.env.NEXT_PUBLIC_DISCORD_SUPPORT!} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| frontEndUrl={process.env.FRONTEND_URL!} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| isGeneral={!!process.env.IS_GENERAL} | ||||||||||||||||||||||||||||||||||||||||||||
| uploadDirectory={process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY!} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare' | |
| } | |
| backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL!} | |
| plontoKey={process.env.NEXT_PUBLIC_POLOTNO!} | |
| billingEnabled={!!process.env.STRIPE_PUBLISHABLE_KEY} | |
| discordUrl={process.env.NEXT_PUBLIC_DISCORD_SUPPORT!} | |
| frontEndUrl={process.env.FRONTEND_URL!} | |
| isGeneral={!!process.env.IS_GENERAL} | |
| uploadDirectory={process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY!} | |
| storageProvider={ | |
| (process.env.STORAGE_PROVIDER as 'local' | 'cloudflare') || 'local' | |
| } | |
| backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL || 'https://default-backend-url.com'} | |
| plontoKey={process.env.NEXT_PUBLIC_POLOTNO || 'defaultPlontoKey'} | |
| billingEnabled={!!process.env.STRIPE_PUBLISHABLE_KEY} | |
| discordUrl={process.env.NEXT_PUBLIC_DISCORD_SUPPORT || 'https://default-discord-url.com'} | |
| frontEndUrl={process.env.FRONTEND_URL || 'https://default-frontend-url.com'} | |
| isGeneral={!!process.env.IS_GENERAL} | |
| uploadDirectory={ | |
| process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY || '/default/upload/directory' | |
| } |
🧰 Tools
🪛 GitHub Check: ESLint
[warning] 41-41: Disallow non-null assertions using the ! postfix operator
Forbidden non-null assertion.
[warning] 43-43: Disallow non-null assertions using the ! postfix operator
Forbidden non-null assertion.
[warning] 44-44: Disallow non-null assertions using the ! postfix operator
Forbidden non-null assertion.
[warning] 46-46: Disallow non-null assertions using the ! postfix operator
Forbidden non-null assertion.
[warning] 47-47: Disallow non-null assertions using the ! postfix operator
Forbidden non-null assertion.
[warning] 49-49: Disallow non-null assertions using the ! postfix operator
Forbidden non-null assertion.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,29 +4,29 @@ import { FC, useCallback, useState } from 'react'; | |
| import clsx from 'clsx'; | ||
| import interClass from '@gitroom/react/helpers/inter.font'; | ||
| import { useVariables } from '@gitroom/react/helpers/variable.context'; | ||
| import { useTranslations } from 'next-intl'; | ||
|
|
||
| const useFaqList = () => { | ||
| const {isGeneral} = useVariables(); | ||
| const t = useTranslations('Billing') | ||
| return [ | ||
| { | ||
| title: `Can I trust ${isGeneral ? 'Postiz' : 'Gitroom'}?`, | ||
| description: `${isGeneral ? 'Postiz' : 'Gitroom'} is proudly open-source! We believe in an ethical and transparent culture, meaning that ${isGeneral ? 'Postiz' : 'Gitroom'} will live forever. You can check out the entire code or use it for personal projects. To view the open-source repository, <a href="https://github.com/gitroomhq/postiz-app" target="_blank" style="text-decoration: underline;">click here</a>.`, | ||
| title: `${t('Can I trust')} ${isGeneral ? 'Postiz' : 'Gitroom'}?`, | ||
| description: `${isGeneral ? 'Postiz' : 'Gitroom'} ${t("is proudly open-source! We believe in an ethical and transparent culture, meaning that")} ${isGeneral ? 'Postiz' : 'Gitroom'} ${t("will live forever")}. ${t("You can check out the entire code or use it for personal projects")}. ${t("To view the open-source repository,")} <a href="https://github.com/gitroomhq/postiz-app" target="_blank" style="text-decoration: underline;">${t("click here")}</a>.`, | ||
|
||
| }, | ||
| { | ||
| title: 'What are channels?', | ||
| title: t('What are channels?'), | ||
| description: `${ | ||
| isGeneral ? 'Postiz' : 'Gitroom' | ||
| } allows you to schedule your posts between different channels. | ||
| A channel is a publishing platform where you can schedule your posts. | ||
| For example, you can schedule your posts on X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads and Pinterest.`, | ||
| } ${t("allows you to schedule your posts between different channels")}. ${t("A channel is a publishing platform where you can schedule your posts")}. ${t("For example, you can schedule your posts on X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads and Pinterest")}.`, | ||
| }, | ||
| { | ||
| title: 'What are team members?', | ||
| description: `If you have a team with multiple members, you can invite them to your workspace to collaborate on your posts and add their personal channels`, | ||
| title: t('What are team members?'), | ||
| description: t(`If you have a team with multiple members, you can invite them to your workspace to collaborate on your posts and add their personal channels`), | ||
| }, | ||
| { | ||
| title: 'What is AI auto-complete?', | ||
| description: `We automate ChatGPT to help you write your social posts and articles`, | ||
| title: t('What is AI auto-complete?'), | ||
| description: t(`We automate ChatGPT to help you write your social posts and articles`), | ||
| }, | ||
| ]; | ||
| } | ||
|
|
@@ -104,10 +104,11 @@ export const FAQSection: FC<{ title: string; description: string }> = ( | |
|
|
||
| export const FAQComponent: FC = () => { | ||
| const list = useFaqList(); | ||
| const t = useTranslations('Billing') | ||
| return ( | ||
| <div> | ||
| <h3 className="text-[24px] text-center mt-[81px] mb-[40px]"> | ||
| Frequently Asked Questions | ||
| {t('FrequentlyAskedQuestions')} | ||
| </h3> | ||
| <div className="gap-[24px] flex-col flex select-none"> | ||
| {list.map((item, index) => ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider reducing cookie expiration time.
A 10-year cookie expiration time is excessive. Consider using a shorter duration (e.g., 1 year) to align with security best practices.
📝 Committable suggestion