Skip to content

Commit

Permalink
unblock lingui and allow for SSR on almost all pages (#3999)
Browse files Browse the repository at this point in the history
## What does this PR do and why?

The big kahuna - this breaks lingui out into SSR and will not block for client side compilation/rendering

## Screenshots or screen recordings

_If applicable, provide screenshots or screen recordings to demonstrate the changes._

## Acceptance checklist

- [ ] I have evaluated the [Approval Guidelines](https://github.com/jbx-protocol/juice-interface/blob/main/CONTRIBUTING.md#approval-guidelines) for this PR.
- [ ] (if relevant) I have tested this PR in [all supported browsers](https://github.com/jbx-protocol/juice-interface/blob/main/CONTRIBUTING.md#supported-browsers).
- [ ] (if relevant) I have tested this PR in dark mode and light mode (if applicable).
  • Loading branch information
wraeth-eth authored Aug 31, 2023
1 parent f776a49 commit dc43f7f
Show file tree
Hide file tree
Showing 40 changed files with 424 additions and 142 deletions.
19 changes: 0 additions & 19 deletions .linguirc.json

This file was deleted.

25 changes: 25 additions & 0 deletions lingui.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { formatter } = require('@lingui/format-po')

const locales = ['en', 'zh']

if (process.env.NODE_ENV !== 'production') {
locales.push('pseudo')
}

/** @type {import('@lingui/conf').LinguiConfig} */
module.exports = {
locales: locales,
sourceLocale: 'en',
pseudoLocale: 'pseudo',
catalogs: [
{
path: 'src/locales/{locale}/messages',
include: ['src'],
},
],
format: formatter({ origins: false, lineNumbers: false }),
orderBy: 'messageId',
fallbackLocales: {
default: 'en',
},
}
19 changes: 18 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
const { withSentryConfig } = require('@sentry/nextjs')
const withBundleAnalyzer = require('@next/bundle-analyzer')
const linguiConfig = require('./lingui.config')

const webpack = require('webpack')

Expand Down Expand Up @@ -112,8 +113,19 @@ const SECURITY_HEADERS = [
}, // NOTE: gnosis safe is still allowed due to frame-ancestors definition
]

/** @type {import('next').NextConfig} */
const nextConfig = removeImports({
experimental: { esmExternals: true },
experimental: {
esmExternals: true,
swcPlugins: [
'@lingui/swc-plugin',
{
runtimeModules: {
i18n: ['@lingui/core', 'i18n'],
},
},
],
},
staticPageGenerationTimeout: 90,
webpack: config => {
config.resolve.fallback = { fs: false, module: false }
Expand All @@ -126,6 +138,11 @@ const nextConfig = removeImports({

return config
},
i18n: {
localeDetection: false,
locales: linguiConfig.locales,
defaultLocale: linguiConfig.sourceLocale,
},
async redirects() {
return [
{
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@
"@graphql-codegen/typescript-operations": "^3.0.3",
"@graphql-codegen/typescript-react-apollo": "^3.3.2",
"@graphql-codegen/typescript-resolvers": "^3.2.0",
"@lingui/loader": "^4.4.0",
"@lingui/swc-plugin": "4.0.4",
"@next/bundle-analyzer": "^13.2.4",
"@testing-library/cypress": "^8.0.2",
"@testing-library/jest-dom": "^5.16.5",
Expand Down
2 changes: 1 addition & 1 deletion src/components/ErrorNotificationButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { WarningOutlined } from '@ant-design/icons'
import { Trans } from '@lingui/macro'
import { Button } from 'antd'
import LanguageProvider from 'contexts/Language/LanguageProvider'
import { LanguageProvider } from 'contexts/Language/LanguageProvider'
import { helpPagePath } from 'utils/routes'
import ExternalLink from './ExternalLink'

Expand Down
2 changes: 2 additions & 0 deletions src/components/Navbar/components/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type LinkItem = {
id: string
label: ReactNode
href: string
locale?: string
isExternal?: boolean
}

Expand Down Expand Up @@ -130,6 +131,7 @@ export const DropdownMenu = ({
<PatchedNextLink
className="text-primary px-4 first:rounded-t-lg last:rounded-b-lg hover:bg-grey-100 dark:hover:bg-slate-600 md:whitespace-nowrap md:py-2.5 md:font-medium md:first:pt-3.5 md:last:pb-3.5"
href={item.href}
locale={item.locale ?? undefined}
>
{item.label}
</PatchedNextLink>
Expand Down
29 changes: 13 additions & 16 deletions src/components/Navbar/components/NavLanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { LanguageIcon } from '@heroicons/react/24/solid'
import { Trans } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { SUPPORTED_LANGUAGES } from 'constants/locale'
import { useCallback, useMemo } from 'react'
import { useRouter } from 'next/router'
import { twMerge } from 'tailwind-merge'
import { reloadWindow } from 'utils/windowUtils'
import { DropdownMenu } from './DropdownMenu'

// Language select tool seen in top nav
Expand All @@ -12,16 +11,10 @@ export default function NavLanguageSelector({
}: {
className?: string
}) {
// Sets the new language with localStorage and reloads the page
const setLanguage = useCallback((newLanguage: string) => {
localStorage.setItem('lang', newLanguage)
reloadWindow()
}, [])

const currentLanguage = useMemo(
() => localStorage.getItem('lang') ?? 'en',
[],
)
const router = useRouter()
const {
i18n: { locale },
} = useLingui()

return (
<DropdownMenu
Expand All @@ -33,15 +26,19 @@ export default function NavLanguageSelector({
<div className="flex items-center gap-4">
<LanguageIcon className="h-6 w-6" />
<span className="font-medium md:hidden">
<Trans>{SUPPORTED_LANGUAGES[currentLanguage].short}</Trans>
{SUPPORTED_LANGUAGES[locale].short}
</span>
</div>
}
items={Object.values(SUPPORTED_LANGUAGES).map(lang => ({
id: lang.code,
label: lang.long,
onClick: () => {
setLanguage(lang.code)
// TODO: We want to use the bottom but due to a bug in t macros we cant
// locale: lang.code,
// href: pathname,
onClick: async () => {
await router.push(router.asPath, router.asPath, { locale: lang.code })
router.reload()
},
}))}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { V2V3_CURRENCY_ETH } from 'utils/v2v3/currency'
import { useCartSummary } from '../hooks/useCartSummary'
import { SummaryCollapsedView } from './SummaryCollapsedView'

jest.mock('contexts/Language/LanguageProvider')

jest.mock('use-resize-observer', () => ({
__esModule: true,
default: jest.fn(() => ({
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProjectDashboard/utils/modals.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { ModalOnCancelFn, ModalOnOkFn } from 'components/modals/JuiceModal'
import LanguageProvider from 'contexts/Language/LanguageProvider'
import { LanguageProvider } from 'contexts/Language/LanguageProvider'
import { ReactNode } from 'react'
import { createRoot } from 'react-dom/client'
import { ConfirmationDeletionModal } from '../components/ui/ConfirmationDeletionModal'
Expand Down
32 changes: 11 additions & 21 deletions src/components/common/CoreAppWrapper/CoreAppWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,11 @@ import ReactQueryProvider from 'contexts/ReactQueryProvider'
import { ThemeProvider } from 'contexts/Theme/ThemeProvider'
import TxHistoryProvider from 'contexts/Transaction/TxHistoryProvider'
import { installJuiceboxWindowObject } from 'lib/juicebox'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import { twJoin } from 'tailwind-merge'
import { redirectTo } from 'utils/windowUtils'

const LanguageProvider = dynamic(
() => import('contexts/Language/LanguageProvider'),
{
ssr: false,
},
)

/**
* Contains all the core app providers used by each page.
*
Expand All @@ -35,19 +27,17 @@ export const AppWrapper: React.FC<
return (
<React.StrictMode>
<ReactQueryProvider>
<LanguageProvider>
<TxHistoryProvider>
<ThemeProvider>
<EtherPriceProvider>
<ArcxProvider>
<QuickProjectSearchProvider>
<_Wrapper hideNav={hideNav}>{children}</_Wrapper>
</QuickProjectSearchProvider>
</ArcxProvider>
</EtherPriceProvider>
</ThemeProvider>
</TxHistoryProvider>
</LanguageProvider>
<TxHistoryProvider>
<ThemeProvider>
<EtherPriceProvider>
<ArcxProvider>
<QuickProjectSearchProvider>
<_Wrapper hideNav={hideNav}>{children}</_Wrapper>
</QuickProjectSearchProvider>
</ArcxProvider>
</EtherPriceProvider>
</ThemeProvider>
</TxHistoryProvider>
</ReactQueryProvider>
</React.StrictMode>
)
Expand Down
73 changes: 23 additions & 50 deletions src/contexts/Language/LanguageProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,33 @@
import { i18n } from '@lingui/core'
import {
detect,
fromNavigator,
fromStorage,
fromUrl,
} from '@lingui/detect-locale'
import { Messages } from '@lingui/core'
import { I18nProvider } from '@lingui/react'
import defaultLocale from 'locales/en/messages'
import { ReactNode } from 'react'
import { useLingUiInit } from 'hooks/useLinguiInit'
import React from 'react'

import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from 'constants/locale'

const getLocale = (): string => {
if (typeof window === 'undefined') return DEFAULT_LOCALE

let locale =
detect(fromUrl('lang'), fromStorage('lang'), fromNavigator()) ??
DEFAULT_LOCALE

if (!SUPPORTED_LOCALES.includes(locale)) {
locale = DEFAULT_LOCALE
}

return locale
}

const activateDefaultLocale = () => {
const { messages } = defaultLocale
i18n.load(DEFAULT_LOCALE, messages)
i18n.activate(DEFAULT_LOCALE)
export type I18nProviderProps = {
children: React.ReactNode
i18n?: { messages: Messages; locale: string }
}

const dynamicActivate = async (locale: string) => {
try {
const { messages } = await import(`../../locales/${locale}/messages`)
let i18nSingleton: { messages: Messages; locale: string } | undefined

i18n.load(locale, messages)
i18n.activate(locale)
} catch (e) {
console.error(`Error loading locale "${locale}:"`, e)
// fall back to default locale
activateDefaultLocale()
export const LanguageProvider: React.FC<I18nProviderProps> = ({
children,
i18n: _i18n,
}) => {
if (_i18n) {
i18nSingleton = _i18n
} else {
_i18n = i18nSingleton
}
}

const locale = getLocale()
if (locale === DEFAULT_LOCALE) {
activateDefaultLocale()
} else {
dynamicActivate(locale)
}
if (!_i18n)
throw new Error(
'i18n must be provided at least once. This is usually done in _app.tsx',
)

const messages = _i18n?.messages ?? []
const locale = _i18n?.locale ?? 'en'
const i18n = useLingUiInit(messages, locale)

export default function LanguageProvider({
children,
}: {
children: ReactNode
}) {
return <I18nProvider i18n={i18n}>{children}</I18nProvider>
}
7 changes: 7 additions & 0 deletions src/contexts/Language/__mocks__/LanguageProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// generate mock for LanguageProvider

import { I18nProviderProps } from '../LanguageProvider'

export const LanguageProvider: React.FC<I18nProviderProps> = ({ children }) => {
return <div>{children}</div>
}
26 changes: 26 additions & 0 deletions src/hooks/useLinguiInit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// https://github.com/lingui/js-lingui/pull/1541

import { i18n, Messages } from '@lingui/core'
import { useEffect } from 'react'

export const useLingUiInit = (messages: Messages, locale: string) => {
const isClient = typeof window !== 'undefined'

if (!isClient && locale !== i18n.locale) {
// there is single instance of i18n on the server
i18n.loadAndActivate({ locale, messages })
}
if (isClient && i18n.locale === undefined) {
// first client render
i18n.loadAndActivate({ locale, messages })
}

useEffect(() => {
const localeDidChange = locale !== i18n.locale
if (localeDidChange) {
i18n.loadAndActivate({ locale, messages })
}
}, [locale, messages])

return i18n
}
5 changes: 5 additions & 0 deletions src/locales/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export async function loadCatalog(locale: string) {
const { messages } = await import(`@lingui/loader!./${locale}/messages.po`)

return messages
}
11 changes: 9 additions & 2 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Head } from 'components/common'
import { LanguageProvider } from 'contexts/Language/LanguageProvider'
import SupabaseSessionProvider from 'contexts/SupabaseSession/SupabaseSessionProvider'
import { initWeb3Onboard, useInitWallet } from 'hooks/Wallet'
import type { AppProps } from 'next/app'
Expand All @@ -15,13 +16,19 @@ export default function MyApp({ Component, pageProps }: AppProps) {
// Currently, init() must be called *here* (as opposed to AppWrapper), or else it breaks when navigating between pages.
useInitWallet()

if (!pageProps.i18n) {
console.error(
'Missing i18n prop - please ensure that page has globalGetServerSideProps',
)
}

return (
<>
<LanguageProvider i18n={pageProps.i18n}>
{/* Default HEAD - overwritten by specific page SEO */}
<Head />
<SupabaseSessionProvider initialSession={pageProps.initialSession}>
<Component {...pageProps} />
</SupabaseSessionProvider>
</>
</LanguageProvider>
)
}
Loading

1 comment on commit dc43f7f

@vercel
Copy link

@vercel vercel bot commented on dc43f7f Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.