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

refactor!: remove dynamic locale switching support #1735

Merged
merged 48 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
74fa8eb
refactor!: remove locale changing
ST-DDT Jan 13, 2023
8ff9c61
chore: remove broken code from tests
ST-DDT Jan 13, 2023
c1c86cd
chore: more test fixes
ST-DDT Jan 14, 2023
6113065
chore: cleanup
ST-DDT Jan 14, 2023
fa9d4d8
chore: remove unused code
ST-DDT Jan 15, 2023
174f7aa
chore: add lost comment again
ST-DDT Jan 15, 2023
b0c4e83
chore: improve email test
ST-DDT Jan 15, 2023
49bb957
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Jan 17, 2023
74dbb5f
chore: Faker class backwards compatibility
ST-DDT Jan 17, 2023
a8d1664
chore: export FakerOptions again
ST-DDT Jan 17, 2023
8948d66
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Jan 18, 2023
6880bd1
chore: explicitly throw for faker.random.locale
ST-DDT Jan 19, 2023
95fdc56
docs: handle private methods
ST-DDT Jan 19, 2023
1dc20a0
chore: cleanup
ST-DDT Jan 19, 2023
bcb10b2
chore: more cleanup
ST-DDT Jan 19, 2023
245ee98
chore: stricter types in all_functional test
ST-DDT Jan 19, 2023
78e866f
chore: merge main
ST-DDT Jan 23, 2023
3b67585
chore: merge next
ST-DDT Jan 26, 2023
179858a
chore: merge next
ST-DDT Jan 29, 2023
e6f36dd
chore: merge next
ST-DDT Jan 30, 2023
ea631fe
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Feb 1, 2023
9a3fae2
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Feb 1, 2023
02b40aa
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Feb 13, 2023
3dc29e2
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Feb 14, 2023
f5bf6b7
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Feb 15, 2023
7332e1b
docs: update documentation
ST-DDT Feb 18, 2023
23823ff
docs: add upgrade guide
ST-DDT Feb 18, 2023
2623110
Merge remote-tracking branch 'origin/next' into refactor/remove-local…
ST-DDT Feb 18, 2023
cf5fed6
chore: commit missing files
ST-DDT Feb 18, 2023
1e3c16f
docs: link to issue directly
ST-DDT Feb 18, 2023
dffc753
chore: apply suggestions and some fixes
ST-DDT Feb 18, 2023
6387d52
chore: fix typo
ST-DDT Feb 18, 2023
858020c
chore: apply suggestions
ST-DDT Feb 19, 2023
c5a4d00
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Feb 22, 2023
af9f201
chore: apply suggetions
ST-DDT Feb 22, 2023
d5a1b56
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Feb 28, 2023
aae21d7
chore: apply team decisions
ST-DDT Feb 28, 2023
349dcfe
chore: repeat test
ST-DDT Mar 1, 2023
00a07b3
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Mar 3, 2023
d404b3b
chore: apply suggestions
ST-DDT Mar 4, 2023
032ea64
Merge remote-tracking branch 'origin/next' into refactor/remove-local…
ST-DDT Mar 4, 2023
45570c0
chore: apply suggestions
ST-DDT Mar 4, 2023
ad9acac
chore: apply suggestions
ST-DDT Mar 4, 2023
e0c6663
chore: apply suggestions
ST-DDT Mar 5, 2023
ed7a76e
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Mar 5, 2023
62e599e
chore: remove debug code
ST-DDT Mar 5, 2023
b166fff
chore: really revert debug code
ST-DDT Mar 5, 2023
f3b1484
Merge branch 'next' into refactor/remove-locale-changing
ST-DDT Mar 7, 2023
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
6 changes: 3 additions & 3 deletions scripts/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { buildSync } from 'esbuild';
import { sync as globSync } from 'glob';
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import locales from '../src/locales';
import { allLocales } from '../src';

console.log('Building dist for node (cjs)...');

Expand All @@ -14,7 +14,7 @@ if (existsSync(localeDir)) {
}

mkdirSync(localeDir);
for (const locale of Object.keys(locales)) {
for (const locale of Object.keys(allLocales)) {
writeFileSync(
`${localeDir}/${locale}.js`,
`module.exports = require('../dist/cjs/locale/${locale}');\n`,
Expand Down Expand Up @@ -43,7 +43,7 @@ console.log('Building dist for node type=module (esm)...');
buildSync({
entryPoints: [
'./src/index.ts',
...Object.keys(locales).map((locale) => `./src/locale/${locale}.ts`),
...Object.keys(allLocales).map((locale) => `./src/locale/${locale}.ts`),
],
outdir: './dist/esm',
bundle: true,
Expand Down
80 changes: 58 additions & 22 deletions scripts/generateLocales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
*
* Run this script using `pnpm run generate:locales`
*/
import { lstatSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import {
existsSync,
lstatSync,
readdirSync,
readFileSync,
writeFileSync,
} from 'node:fs';
import { resolve } from 'node:path';
import type { Options } from 'prettier';
import { format } from 'prettier';
Expand All @@ -24,6 +30,7 @@ import type { Definitions, LocaleDefinition } from '../src/definitions';
const pathRoot = resolve(__dirname, '..');
const pathLocale = resolve(pathRoot, 'src', 'locale');
const pathLocales = resolve(pathRoot, 'src', 'locales');
const pathLocaleIndex = resolve(pathLocale, 'index.ts');
const pathLocalesIndex = resolve(pathLocales, 'index.ts');
const pathDocsGuideLocalization = resolve(
pathRoot,
Expand Down Expand Up @@ -107,19 +114,31 @@ function escapeField(module: string): string {
}

function generateLocaleFile(locale: string): void {
const parts = locale.split('_');
const locales = [locale];

for (let i = parts.length - 1; i > 0; i--) {
const fallback = parts.slice(0, i).join('_');
if (existsSync(resolve(pathLocales, fallback))) {
locales.push(fallback);
}
}
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

if (locales[locales.length - 1] !== 'en') {
locales.push('en');
}

let content = `
${autoGeneratedCommentHeader}

import { Faker } from '../faker';
import ${locale} from '../locales/${locale}';
${locale !== 'en' ? "import en from '../locales/en';" : ''}
${locales
.map((imp) => `import ${imp} from '../locales/${imp}';`)
.join('\n')}

export const faker = new Faker({
locale: '${locale}',
localeFallback: 'en',
locales: {
${locale},
${locale !== 'en' ? 'en,' : ''}
locale: ${
locales.length === 1 ? locales[0] : `[${locales.join(', ')}]`
},
});
`;
Expand Down Expand Up @@ -273,9 +292,10 @@ function updateLocaleFileHook(
const locales = readdirSync(pathLocales);
removeIndexTs(locales);

let localeIndexImports = "import type { LocaleDefinition } from '..';\n";
let localeIndexType = 'export type KnownLocale =\n';
let localeIndexLocales = 'const locales: KnownLocales = {\n';
let localeIndexImports = '';
let localeIndexExportsIndividual = '';
let localeIndexExportsGrouped = '';
let localesIndexExports = '';

let localizationLocales = '| Locale | Name |\n| :--- | :--- |\n';

Expand All @@ -287,9 +307,14 @@ for (const locale of locales) {
const localeTitle = localeDef?.title ?? `TODO: Insert Title for ${locale}`;
const localeSeparator = localeDef?.separator;

localeIndexImports += `import ${locale} from './${locale}';\n`;
localeIndexType += ` | '${locale}'\n`;
localeIndexLocales += ` ${locale},\n`;
const capitalizedLocale = locale.replace(/^([a-z]+)/, (part) =>
part.toUpperCase()
);

localeIndexImports += `import { faker as faker${capitalizedLocale} } from './${locale}';\n`;
localeIndexExportsIndividual += ` faker${capitalizedLocale},\n`;
localeIndexExportsGrouped += ` ${locale}: faker${capitalizedLocale},\n`;
localesIndexExports += `export { default as ${locale} } from './${locale}';\n`;
localizationLocales += `| ${locale} | ${localeTitle} |\n`;

// src/locale/<locale>.ts
Expand All @@ -307,24 +332,35 @@ for (const locale of locales) {
);
}

// src/locales/index.ts
// src/locale/index.ts

let indexContent = `
let localeIndexContent = `
${autoGeneratedCommentHeader}

${localeIndexImports}

${localeIndexType};
export {
${localeIndexExportsIndividual}
};

export const allFakers = {
${localeIndexExportsGrouped}
} as const;
`;

export type KnownLocales = Record<KnownLocale, LocaleDefinition>;
localeIndexContent = format(localeIndexContent, prettierTsOptions);
writeFileSync(pathLocaleIndex, localeIndexContent);

${localeIndexLocales}};
// src/locales/index.ts

let localesIndexContent = `
${autoGeneratedCommentHeader}

export default locales;
${localesIndexExports}
`;

indexContent = format(indexContent, prettierTsOptions);
writeFileSync(pathLocalesIndex, indexContent);
localesIndexContent = format(localesIndexContent, prettierTsOptions);
writeFileSync(pathLocalesIndex, localesIndexContent);

// docs/guide/localization.md

Expand Down
149 changes: 11 additions & 138 deletions src/faker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { FakerError } from './errors/faker-error';
import { deprecated } from './internal/deprecated';
import type { Mersenne } from './internal/mersenne/mersenne';
import mersenne from './internal/mersenne/mersenne';
import type { KnownLocale } from './locales';
import { AnimalModule } from './modules/animal';
import { ColorModule } from './modules/color';
import { CommerceModule } from './modules/commerce';
Expand Down Expand Up @@ -31,56 +30,10 @@ import { StringModule } from './modules/string';
import { SystemModule } from './modules/system';
import { VehicleModule } from './modules/vehicle';
import { WordModule } from './modules/word';
import type { LiteralUnion } from './utils/types';

export type UsableLocale = LiteralUnion<KnownLocale>;
export type UsedLocales = Partial<Record<UsableLocale, LocaleDefinition>>;

export interface FakerOptions {
locales: UsedLocales;
locale?: UsableLocale;
localeFallback?: UsableLocale;
}

const metadataKeys: ReadonlyArray<keyof LocaleDefinition> = [
'title',
'separator',
];
import { mergeLocales } from './utils/merge-locales';

export class Faker {
locales: UsedLocales;
private _locale: UsableLocale;
private _localeFallback: UsableLocale;

get locale(): UsableLocale {
return this._locale;
}

set locale(locale: UsableLocale) {
if (!this.locales[locale]) {
throw new FakerError(
`Locale ${locale} is not supported. You might want to add the requested locale first to \`faker.locales\`.`
);
}

this._locale = locale;
}

get localeFallback(): UsableLocale {
return this._localeFallback;
}

set localeFallback(localeFallback: UsableLocale) {
if (!this.locales[localeFallback]) {
throw new FakerError(
`Locale ${localeFallback} is not supported. You might want to add the requested locale first to \`faker.locales\`.`
);
}

this._localeFallback = localeFallback;
}

readonly definitions: LocaleDefinition = this.initDefinitions();
readonly definitions: LocaleDefinition;

/** @internal */
private readonly _mersenne: Mersenne = mersenne();
Expand Down Expand Up @@ -137,91 +90,20 @@ export class Faker {
return this.person;
}

constructor(opts: FakerOptions) {
if (!opts) {
throw new FakerError(
'Options with at least one entry in locales must be provided'
);
}

if (Object.keys(opts.locales ?? {}).length === 0) {
throw new FakerError(
'At least one entry in locales must be provided in the locales parameter'
);
}

this.locales = opts.locales;
this.locale = opts.locale || 'en';
this.localeFallback = opts.localeFallback || 'en';
}

/**
* Creates a Proxy based LocaleDefinition that virtually merges the locales.
*/
private initDefinitions(): LocaleDefinition {
// Returns the first LocaleDefinition[key] in any locale
const resolveBaseData = (key: keyof LocaleDefinition): unknown =>
this.locales[this.locale][key] ?? this.locales[this.localeFallback][key];
constructor(options: { locale: LocaleDefinition | LocaleDefinition[] }) {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
let { locale } = options;

// Returns the first LocaleDefinition[module][entry] in any locale
const resolveModuleData = (
module: keyof LocaleDefinition,
entry: string
): unknown =>
this.locales[this.locale][module]?.[entry] ??
this.locales[this.localeFallback][module]?.[entry];

// Returns a proxy that can return the entries for a module (if it exists)
const moduleLoader = (
module: keyof LocaleDefinition
): Record<string, unknown> | undefined => {
if (resolveBaseData(module)) {
return new Proxy(
{},
{
get(target, entry: string): unknown {
return resolveModuleData(module, entry);
},
}
if (Array.isArray(locale)) {
if (locale.length === 0) {
throw new FakerError(
'The locale option must contain at least one locale definition.'
);
} else {
return undefined;
}
};

return new Proxy({} as LocaleDefinition, {
get(target: LocaleDefinition, module: string): unknown {
// Support aliases
if (module === 'address') {
module = 'location';
deprecated({
deprecated: `faker.helpers.fake('{{address.*}}') or faker.definitions.address`,
proposed: `faker.helpers.fake('{{location.*}}') or faker.definitions.location`,
since: '8.0',
until: '10.0',
});
} else if (module === 'name') {
module = 'person';
deprecated({
deprecated: `faker.helpers.fake('{{name.*}}') or faker.definitions.name`,
proposed: `faker.helpers.fake('{{person.*}}') or faker.definitions.person`,
since: '8.0',
until: '10.0',
});
}
locale = mergeLocales(locale);
}

let result = target[module];
if (result) {
return result;
} else if (metadataKeys.includes(module)) {
return resolveBaseData(module);
} else {
result = moduleLoader(module);
target[module] = result;
return result;
}
},
});
this.definitions = locale;
}

/**
Expand Down Expand Up @@ -295,13 +177,4 @@ export class Faker {

return seed;
}

/**
* Set Faker's locale
*
* @param locale The locale to set (e.g. `en` or `en_AU`, `en_AU_ocker`).
*/
setLocale(locale: UsableLocale): void {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
this.locale = locale;
}
}
15 changes: 5 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { Faker } from './faker';
import allLocales from './locales';

export type {
AnimalDefinitions,
ColorDefinitions,
Expand Down Expand Up @@ -34,7 +31,11 @@ export type {
WordDefinitions,
} from './definitions';
export { FakerError } from './errors/faker-error';
export type { FakerOptions, UsableLocale, UsedLocales } from './faker';
export { Faker } from './faker';
export * from './locale';
export { fakerEN as faker } from './locale';
export * from './locales';
export * as allLocales from './locales';
Shinigami92 marked this conversation as resolved.
Show resolved Hide resolved
export type { AnimalModule } from './modules/animal';
export type {
Casing,
Expand Down Expand Up @@ -78,9 +79,3 @@ export type { StringModule } from './modules/string';
export type { SystemModule } from './modules/system';
export type { VehicleModule } from './modules/vehicle';
export type { WordModule } from './modules/word';
export { Faker };

// since we are requiring the top level of faker, load all locales by default
export const faker: Faker = new Faker({
locales: allLocales,
});
7 changes: 1 addition & 6 deletions src/locale/af_ZA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,5 @@ import af_ZA from '../locales/af_ZA';
import en from '../locales/en';

export const faker = new Faker({
locale: 'af_ZA',
localeFallback: 'en',
locales: {
af_ZA,
en,
},
locale: [af_ZA, en],
});
Loading