Skip to content

Commit

Permalink
feat: i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholasio committed Jul 23, 2024
1 parent ba75f92 commit 7cf4563
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 27 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dev:app": "turbo run dev --parallel --filter=./projects/wp-nextjs-app --filter=./packages/core --filter=./packages/next",
"dev:app:multisite": "turbo run dev --parallel --filter=./projects/wp-multisite-nextjs-app --filter=./packages/core --filter=./packages/next",
"dev:multisite": "turbo run dev --parallel --filter=./projects/wp-multisite-nextjs --filter=./packages/core --filter=./packages/next",
"dev:multisite:i18n": "turbo run dev --parallel --filter=./projects/wp-multisite-i18n-nextjs --filter=./packages/core --filter=./packages/next",
"prepare": "husky install",
"typedoc": "typedoc",
"typedoc:watch": "typedoc --watch",
Expand Down
16 changes: 15 additions & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,21 @@ export interface Integration {

export interface YoastSEOIntegration extends Integration {}

export interface PolylangIntegration extends Integration {}
export interface PolylangIntegration extends Integration {
/**
* The list of locales supported by Polylang
*
* In the app router this will be used to match the locale
*
* In the pages router it will be used to set up the locale config
*/
locales?: string[];

/**
* The default locale for Polylang
*/
defaultLocale?: string;
}

export type Integrations = {
yoastSEO?: YoastSEOIntegration;
Expand Down
19 changes: 18 additions & 1 deletion packages/next/src/config/withHeadstartWPConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export function withHeadstartWPConfig(
}
});

return {
const config: NextConfig = {
...nextConfig,
images: {
...nextConfig.images,
Expand Down Expand Up @@ -285,6 +285,23 @@ export function withHeadstartWPConfig(
return config;
},
};

// if polylang is enabled
// and has locales
// and locales has not been set by the incoming nextConfig
// then do it!
if (
headlessConfig.integrations?.polylang?.enable &&
(headlessConfig.integrations?.polylang?.locales?.length ?? 0) > 0 &&
!nextConfig.i18n
) {
nextConfig.i18n = {
locales: headlessConfig.integrations.polylang.locales as string[],
defaultLocale: headlessConfig.integrations.polylang.defaultLocale ?? 'en',
};
}

return config;
}

export function withHeadlessConfig(
Expand Down
74 changes: 73 additions & 1 deletion packages/next/src/middlewares/__tests__/appMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextRequest } from 'next/server';
import { setHeadstartWPConfig } from '@headstartwp/core/utils';
import { AppMiddleware } from '../appMidleware';
import { AppMiddleware, getAppRouterLocale } from '../appMidleware';

describe('appMiddleware', () => {
it('adds headers', async () => {
Expand Down Expand Up @@ -280,10 +280,82 @@ describe('appMiddleware', () => {

const res = await AppMiddleware(req, { appRouter: true });

expect(getAppRouterLocale(req)).toBe('pt');

expect(res.headers.get('x-middleware-rewrite')).toBe(
'http://test2.com/test2.com/post-name',
);
expect(res.headers.get('x-headstartwp-site')).toBe('test2.com');
expect(res.headers.get('x-headstartwp-locale')).toBe('pt');
});

it('supports locales with polylang and app router', async () => {
setHeadstartWPConfig({
sourceUrl: 'http://testwp.com',
hostUrl: 'http://test.com',
integrations: {
polylang: {
enable: true,
locales: ['en', 'es'],
defaultLocale: 'en',
},
},
});

const req = new NextRequest('http://test.com/post-name', {
method: 'GET',
});

req.headers.set('accept-language', 'es');

expect(getAppRouterLocale(req)).toBe('es');

const res = await AppMiddleware(req, { appRouter: true });

expect(res.headers.get('x-headstartwp-locale')).toBe('es');
});

it('throws on polylang and multisite together', async () => {
setHeadstartWPConfig({
sourceUrl: 'http://testwp.com',
hostUrl: 'http://test.com',
integrations: {
polylang: {
enable: true,
locales: ['en', 'es'],
defaultLocale: 'en',
},
},
sites: [
{
sourceUrl: 'http://testwp.com',
hostUrl: 'http://test.com',
locale: 'en',
},
{
sourceUrl: 'http://testwp2.com',
hostUrl: 'http://test2.com',
locale: 'pt',
},
{
sourceUrl: 'http://testwp2.com/en',
hostUrl: 'http://test2.com',
locale: 'en',
},
],
});

const req = new NextRequest('http://test.com/post-name', {
method: 'GET',
});

req.headers.set('host', 'test.com');
req.headers.set('accept-language', 'es');

expect(getAppRouterLocale(req)).toBeUndefined();

await expect(() => AppMiddleware(req, { appRouter: true })).rejects.toThrow(
'Polylang and multisite are not supported together',
);
});
});
62 changes: 51 additions & 11 deletions packages/next/src/middlewares/appMidleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,47 @@ function isInternalRequest(req: NextRequest) {
return req.nextUrl.pathname.startsWith('/_next');
}

function getAppRouterLocale(request: NextRequest) {
function hasMultisiteConfig() {
const config = getHeadstartWPConfig();
// remove duples
const sitesLocales = config.sites
?.filter((site) => typeof site.locale !== 'undefined')
.map((site) => site.locale as string);
return (config.sites?.length ?? 0) > 0;
}

if (!config.locale) {
function isPolylangIntegrationEnabled() {
const config = getHeadstartWPConfig();
return config.integrations?.polylang?.enable ?? false;
}

export function getAppRouterLocale(request: NextRequest) {
const config = getHeadstartWPConfig();
const isPotentiallyMultisite = hasMultisiteConfig();
const hasPolylangIntegration = isPolylangIntegrationEnabled();

let defaultLocale: string | undefined;
let supportedLocales: string[] = [];

// no polylang, the default locale is the first root locale
if (!hasPolylangIntegration && isPotentiallyMultisite) {
defaultLocale = config.locale ?? 'en';
supportedLocales = [
...new Set(
config.sites
?.filter((site) => typeof site.locale !== 'undefined')
.map((site) => site.locale as string),
),
];
}

// polylang only
if (hasPolylangIntegration && !isPotentiallyMultisite) {
defaultLocale = config.integrations?.polylang?.defaultLocale ?? 'en';
supportedLocales = [...new Set(config.integrations?.polylang?.locales ?? [])];
}

if (typeof defaultLocale === 'undefined') {
return undefined;
}

if (!sitesLocales) {
if (supportedLocales.length === 0) {
return undefined;
}

Expand All @@ -35,9 +64,7 @@ function getAppRouterLocale(request: NextRequest) {
negotiatorHeaders[key] = value;
});

const defaultLocale = config.locale ?? 'en';

const locales: readonly string[] = [defaultLocale, ...(sitesLocales ?? [])];
const locales: readonly string[] = [defaultLocale, ...(supportedLocales ?? [])];

// Use negotiator and intl-localematcher to get best locale
const languages = new Negotiator({ headers: negotiatorHeaders }).languages(locales);
Expand All @@ -62,9 +89,20 @@ export async function AppMiddleware(
return response;
}

const isPotentiallyMultisite = hasMultisiteConfig();
const hasPolylangIntegration = isPolylangIntegrationEnabled();

if (hasPolylangIntegration && isPotentiallyMultisite) {
// potentially conflicting set up
// will figure out later if we need to support this
throw new Error('Polylang and multisite are not supported together');
}

const locale = options.appRouter ? getAppRouterLocale(req) : req.nextUrl.locale;
const hostname = req.headers.get('host') || '';
const site = getSiteByHost(hostname, locale);

// if it's polylang integration, we should not be using locale to get site
const site = getSiteByHost(hostname, !hasPolylangIntegration ? locale : undefined);
const isMultisiteRequest = site !== null && typeof site.sourceUrl !== 'undefined';

const {
Expand Down Expand Up @@ -105,12 +143,14 @@ export async function AppMiddleware(
url,
),
);

response.headers.set('x-headstartwp-site', hostname);
}

if (locale) {
response.headers.set('x-headstartwp-locale', locale);
}

response.headers.set('x-headstartwp-current-url', currentUrl);

return response;
Expand Down
Empty file.
3 changes: 0 additions & 3 deletions projects/wp-multisite-i18n-nextjs/babel.config.js

This file was deleted.

6 changes: 2 additions & 4 deletions projects/wp-multisite-i18n-nextjs/next.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
const { withHeadlessConfig } = require('@headstartwp/next/config');
const { withHeadstartWPConfig } = require('@headstartwp/next/config');

const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});

const headlessConfig = require('./headless.config');

/**
* Update whatever you need within the nextConfig object.
*
Expand All @@ -22,4 +20,4 @@ const nextConfig = {
},
};

module.exports = withBundleAnalyzer(withHeadlessConfig(nextConfig, headlessConfig));
module.exports = withBundleAnalyzer(withHeadstartWPConfig(nextConfig));
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ const navStyles = css`
`;

export const Nav = () => {
const { data, loading, error } = useMenu('primary', {
// these settings will re-render menu client side to ensure
// it always have the latest items
revalidateOnMount: true,
revalidateOnFocus: true,
});
const { data, loading, error } = useMenu('primary');

if (loading || error) {
return null;
Expand Down

0 comments on commit 7cf4563

Please sign in to comment.