Skip to content

Commit

Permalink
feat(i18n): better APIs (#8908)
Browse files Browse the repository at this point in the history
Co-authored-by: Elian ☕️ <[email protected]>
  • Loading branch information
ematipico and ElianCodes committed Nov 1, 2023
1 parent b14eb7f commit 8c54945
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 35 deletions.
71 changes: 65 additions & 6 deletions packages/astro/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,72 @@ declare module 'astro:transitions/client' {
declare module 'astro:i18n' {
type I18nModule = typeof import('./dist/i18n/index.js');

// TODO: documentation
export const getLocaleRelativeUrl: (locale: string) => string;
export const getLocaleAbsoluteUrl: (locale: string) => string;
export type GetLocaleOptions = import('./dist/i18n/index.js').GetLocaleOptions;

// TODO: documentation
export const getLocaleRelativeUrlList: () => string[];
export const getLocaleAbsoluteUrlList: () => string[];
/**
* @param {string} locale A locale
* @param {import('./dist/i18n/index.js').GetLocaleOptions} options Customise the generated path
* @return {string}
*
* Returns a _relative_ path with passed locale.
*
* ## Errors
*
* Throws an error if the locale doesn't exist in the list of locales defined in the configuration.
*
* ## Examples
*
* ```js
* import { getLocaleRelativeUrl } from "astro:i18n";
* getLocaleRelativeUrl("es"); // /es
* getLocaleRelativeUrl("es", { path: "getting-started" }); // /es/getting-started
* getLocaleRelativeUrl("es", { path: "getting-started", prependWith: "blog" }); // /blog/es/getting-started
* getLocaleRelativeUrl("es_US", { path: "getting-started", prependWith: "blog", normalizeLocale: true }); // /blog/es-us/getting-started
* ```
*/
export const getLocaleRelativeUrl: (locale: string, options?: GetLocaleOptions) => string;

/**
*
* @param {string} locale A locale
* @param {import('./dist/i18n/index.js').GetLocaleOptions} options Customise the generated path
* @return {string}
*
* Returns an absolute path with the passed locale. The behaviour is subject to change based on `site` configuration.
* If _not_ provided, the function will return a _relative_ URL.
*
* ## Errors
*
* Throws an error if the locale doesn't exist in the list of locales defined in the configuration.
*
* ## Examples
*
* If `site` is `https://example.com`:
*
* ```js
* import { getLocaleAbsoluteUrl } from "astro:i18n";
* getLocaleAbsoluteUrl("es"); // https://example.com/es
* getLocaleAbsoluteUrl("es", { path: "getting-started" }); // https://example.com/es/getting-started
* getLocaleAbsoluteUrl("es", { path: "getting-started", prependWith: "blog" }); // https://example.com/blog/es/getting-started
* getLocaleAbsoluteUrl("es_US", { path: "getting-started", prependWith: "blog", normalizeLocale: true }); // https://example.com/blog/es-us/getting-started
* ```
*/
export const getLocaleAbsoluteUrl: (locale: string, options?: GetLocaleOptions) => string;

/**
* @param {import('./dist/i18n/index.js').GetLocaleOptions} options Customise the generated path
* @return {string[]}
*
* Works like `getLocaleRelativeUrl` but it emits the relative URLs for ALL locales:
*/
export const getLocaleRelativeUrlList: (options?: GetLocaleOptions) => string[];
/**
* @param {import('./dist/i18n/index.js').GetLocaleOptions} options Customise the generated path
* @return {string[]}
*
* Works like `getLocaleAbsoluteUrl` but it emits the absolute URLs for ALL locales:
*/
export const getLocaleAbsoluteUrlList: (options?: GetLocaleOptions) => string[];
}

declare module 'astro:middleware' {
Expand Down
44 changes: 34 additions & 10 deletions packages/astro/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,31 @@ import { AstroError } from '../core/errors/index.js';
import { MissingLocale } from '../core/errors/errors-data.js';
import { shouldAppendForwardSlash } from '../core/build/util.js';
import type { AstroConfig } from '../@types/astro.js';
import { joinPaths } from '@astrojs/internal-helpers/path';
import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path';

type GetLocaleRelativeUrl = {
type GetLocaleRelativeUrl = GetLocaleOptions & {
locale: string;
base: string;
locales: string[];
trailingSlash: AstroConfig['trailingSlash'];
format: AstroConfig['build']['format'];
};

export type GetLocaleOptions = {
/**
* Makes the locale URL-friendly by replacing underscores with dashes, and converting the locale to lower case.
*/
normalizeLocale?: boolean;
/**
* An optional path to add after the `locale`
*/
path?: string;
/**
* An optional path to prepend to `locale`
*/
prependWith?: string;
};

type GetLocaleAbsoluteUrl = GetLocaleRelativeUrl & {
site: AstroConfig['site'];
};
Expand All @@ -24,6 +39,9 @@ export function getLocaleRelativeUrl({
locales,
trailingSlash,
format,
path,
prependWith,
normalizeLocale = false,
}: GetLocaleRelativeUrl) {
if (!locales.includes(locale)) {
throw new AstroError({
Expand All @@ -32,11 +50,11 @@ export function getLocaleRelativeUrl({
});
}

const normalizedLocale = normalizeLocale(locale);
const normalizedLocale = normalizeTheLocale(locale, normalizeLocale);
if (shouldAppendForwardSlash(trailingSlash, format)) {
return `${base}${normalizedLocale}/`;
return appendForwardSlash(joinPaths(base, prependWith, normalizedLocale, path));
} else {
return `${base}/${normalizedLocale}`;
return joinPaths(base, prependWith, normalizedLocale, path);
}
}

Expand All @@ -52,7 +70,7 @@ export function getLocaleAbsoluteUrl({ site, ...rest }: GetLocaleAbsoluteUrl) {
}
}

type GetLocalesBaseUrl = {
type GetLocalesBaseUrl = GetLocaleOptions & {
base: string;
locales: string[];
trailingSlash: AstroConfig['trailingSlash'];
Expand All @@ -64,13 +82,16 @@ export function getLocaleRelativeUrlList({
locales,
trailingSlash,
format,
path,
prependWith,
normalizeLocale = false,
}: GetLocalesBaseUrl) {
return locales.map((locale) => {
const normalizedLocale = normalizeLocale(locale);
const normalizedLocale = normalizeTheLocale(locale, normalizeLocale);
if (shouldAppendForwardSlash(trailingSlash, format)) {
return `${base}${normalizedLocale}/`;
return appendForwardSlash(joinPaths(base, prependWith, normalizedLocale, path));
} else {
return `${base}/${normalizedLocale}`;
return joinPaths(base, prependWith, normalizedLocale, path);
}
});
}
Expand All @@ -92,6 +113,9 @@ export function getLocaleAbsoluteUrlList({ site, ...rest }: GetLocaleAbsoluteUrl
* - replaces the `_` with a `-`;
* - transforms all letters to be lower case;
*/
function normalizeLocale(locale: string): string {
function normalizeTheLocale(locale: string, shouldNormalize: boolean): string {
if (!shouldNormalize) {
return locale;
}
return locale.replaceAll('_', '-').toLowerCase();
}
9 changes: 5 additions & 4 deletions packages/astro/src/i18n/vite-plugin-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ export default function astroInternalization({ settings }: AstroInternalization)
const format = ${JSON.stringify(settings.config.build.format)};
const site = ${JSON.stringify(settings.config.site)};
export const getLocaleRelativeUrl = (locale) => _getLocaleRelativeUrl({ locale, base, locales, trailingSlash, format });
export const getLocaleRelativeUrlList = () => _getLocaleRelativeUrlList({ base, locales, trailingSlash, format });
export const getLocaleAbsoluteUrl = (locale) => _getLocaleAbsoluteUrl({ locale, base, locales, trailingSlash, format, site });
export const getLocaleAbsoluteUrlList = () => _getLocaleAbsoluteUrlList({ base, locales, trailingSlash, format, site });
export const getLocaleRelativeUrl = (locale, opts) => _getLocaleRelativeUrl({ locale, base, locales, trailingSlash, format, ...opts });
export const getLocaleAbsoluteUrl = (locale, opts) => _getLocaleAbsoluteUrl({ locale, base, locales, trailingSlash, format, site, ...opts });
export const getLocaleRelativeUrlList = (opts) => _getLocaleRelativeUrlList({ base, locales, trailingSlash, format, ...opts });
export const getLocaleAbsoluteUrlList = (opts) => _getLocaleAbsoluteUrlList({ base, locales, trailingSlash, format, site, ...opts });
`;
}
},
Expand Down
52 changes: 37 additions & 15 deletions packages/astro/test/units/i18n/astro_i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ describe('getLocaleRelativeUrl', () => {
trailingSlash: 'always',
format: 'directory',
})
).to.eq('/blog/en_US/');

expect(
getLocaleRelativeUrl({
locale: 'en_US',
base: '/blog/',
locales: config.experimental.i18n.locales,
trailingSlash: 'always',
format: 'directory',
normalizeLocale: true,
})
).to.eq('/blog/en-us/');

expect(
Expand All @@ -225,7 +236,7 @@ describe('getLocaleRelativeUrl', () => {
trailingSlash: 'always',
format: 'directory',
})
).to.eq('/blog/en-au/');
).to.eq('/blog/en_AU/');
});
});

Expand All @@ -252,7 +263,7 @@ describe('getLocaleRelativeUrlList', () => {
trailingSlash: 'never',
format: 'directory',
})
).to.have.members(['/blog/en', '/blog/en-us', '/blog/es']);
).to.have.members(['/blog/en', '/blog/en_US', '/blog/es']);
});

it('should retrieve the correct list of base URL with locales [format: directory, trailingSlash: always]', () => {
Expand All @@ -277,7 +288,7 @@ describe('getLocaleRelativeUrlList', () => {
trailingSlash: 'always',
format: 'directory',
})
).to.have.members(['/blog/en/', '/blog/en-us/', '/blog/es/']);
).to.have.members(['/blog/en/', '/blog/en_US/', '/blog/es/']);
});

it('should retrieve the correct list of base URL with locales [format: file, trailingSlash: always]', () => {
Expand All @@ -302,7 +313,7 @@ describe('getLocaleRelativeUrlList', () => {
trailingSlash: 'always',
format: 'file',
})
).to.have.members(['/blog/en/', '/blog/en-us/', '/blog/es/']);
).to.have.members(['/blog/en/', '/blog/en_US/', '/blog/es/']);
});

it('should retrieve the correct list of base URL with locales [format: file, trailingSlash: never]', () => {
Expand All @@ -327,7 +338,7 @@ describe('getLocaleRelativeUrlList', () => {
trailingSlash: 'never',
format: 'file',
})
).to.have.members(['/blog/en', '/blog/en-us', '/blog/es']);
).to.have.members(['/blog/en', '/blog/en_US', '/blog/es']);
});

it('should retrieve the correct list of base URL with locales [format: file, trailingSlash: ignore]', () => {
Expand All @@ -352,7 +363,7 @@ describe('getLocaleRelativeUrlList', () => {
trailingSlash: 'ignore',
format: 'file',
})
).to.have.members(['/blog/en', '/blog/en-us', '/blog/es']);
).to.have.members(['/blog/en', '/blog/en_US', '/blog/es']);
});

it('should retrieve the correct list of base URL with locales [format: directory, trailingSlash: ignore]', () => {
Expand All @@ -377,7 +388,7 @@ describe('getLocaleRelativeUrlList', () => {
trailingSlash: 'ignore',
format: 'directory',
})
).to.have.members(['/blog/en/', '/blog/en-us/', '/blog/es/']);
).to.have.members(['/blog/en/', '/blog/en_US/', '/blog/es/']);
});
});

Expand Down Expand Up @@ -598,7 +609,7 @@ describe('getLocaleAbsoluteUrl', () => {
trailingSlash: 'always',
format: 'directory',
})
).to.eq('/blog/en-us/');
).to.eq('/blog/en_US/');

expect(
getLocaleRelativeUrl({
Expand All @@ -608,7 +619,18 @@ describe('getLocaleAbsoluteUrl', () => {
trailingSlash: 'always',
format: 'directory',
})
).to.eq('/blog/en-au/');
).to.eq('/blog/en_AU/');

expect(
getLocaleRelativeUrl({
locale: 'en_US',
base: '/blog/',
locales: config.experimental.i18n.locales,
trailingSlash: 'always',
format: 'directory',
normalizeLocale: true,
})
).to.eq('/blog/en-us/');
});
});

Expand Down Expand Up @@ -638,7 +660,7 @@ describe('getLocaleAbsoluteUrlList', () => {
})
).to.have.members([
'https://example.com/blog/en',
'https://example.com/blog/en-us',
'https://example.com/blog/en_US',
'https://example.com/blog/es',
]);
});
Expand Down Expand Up @@ -668,7 +690,7 @@ describe('getLocaleAbsoluteUrlList', () => {
})
).to.have.members([
'https://example.com/blog/en/',
'https://example.com/blog/en-us/',
'https://example.com/blog/en_US/',
'https://example.com/blog/es/',
]);
});
Expand Down Expand Up @@ -698,7 +720,7 @@ describe('getLocaleAbsoluteUrlList', () => {
})
).to.have.members([
'https://example.com/blog/en/',
'https://example.com/blog/en-us/',
'https://example.com/blog/en_US/',
'https://example.com/blog/es/',
]);
});
Expand Down Expand Up @@ -728,7 +750,7 @@ describe('getLocaleAbsoluteUrlList', () => {
})
).to.have.members([
'https://example.com/blog/en',
'https://example.com/blog/en-us',
'https://example.com/blog/en_US',
'https://example.com/blog/es',
]);
});
Expand Down Expand Up @@ -758,7 +780,7 @@ describe('getLocaleAbsoluteUrlList', () => {
})
).to.have.members([
'https://example.com/blog/en',
'https://example.com/blog/en-us',
'https://example.com/blog/en_US',
'https://example.com/blog/es',
]);
});
Expand Down Expand Up @@ -788,7 +810,7 @@ describe('getLocaleAbsoluteUrlList', () => {
})
).to.have.members([
'https://example.com/blog/en/',
'https://example.com/blog/en-us/',
'https://example.com/blog/en_US/',
'https://example.com/blog/es/',
]);
});
Expand Down

0 comments on commit 8c54945

Please sign in to comment.