Implement Translator: i18n system
This commit is contained in:
parent
5f62894d56
commit
8a2767ef40
@ -8,7 +8,7 @@ module.exports = {
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
],
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
@ -23,7 +23,7 @@ module.exports = {
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"svelte3"
|
||||
"svelte3",
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
@ -33,6 +33,7 @@ module.exports = {
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"eol-last": ["error", "always"],
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"no-throw-literal": "error",
|
||||
// TODO: remove those ignored rules and write a stronger code!
|
||||
|
1
front/dist/resources/translations/.gitignore
vendored
Normal file
1
front/dist/resources/translations/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.json
|
@ -21,6 +21,7 @@
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"jasmine": "^3.5.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"merge-jsons-webpack-plugin": "^2.0.1",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
@ -25,6 +25,7 @@ export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) |
|
||||
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
||||
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
|
||||
export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER;
|
||||
const FALLBACK_LANGUAGE: string = process.env.FALLBACK_LANGUAGE || "en-US";
|
||||
|
||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||
|
||||
@ -44,4 +45,5 @@ export {
|
||||
TURN_PASSWORD,
|
||||
JITSI_URL,
|
||||
JITSI_PRIVATE_MODE,
|
||||
FALLBACK_LANGUAGE,
|
||||
};
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { GameScene } from "./GameScene";
|
||||
import { get } from "svelte/store";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import type { Room } from "../../Connexion/Room";
|
||||
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
||||
import { requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
|
||||
import { menuIconVisiblilityStore } from "../../Stores/MenuStore";
|
||||
import { EnableCameraSceneName } from "../Login/EnableCameraScene";
|
||||
import { LoginSceneName } from "../Login/LoginScene";
|
||||
import { SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||
import { EnableCameraSceneName } from "../Login/EnableCameraScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { get } from "svelte/store";
|
||||
import { requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
|
||||
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
||||
import { menuIconVisiblilityStore } from "../../Stores/MenuStore";
|
||||
import { GameScene } from "./GameScene";
|
||||
|
||||
/**
|
||||
* This class should be responsible for any scene starting/stopping
|
||||
|
@ -4,6 +4,7 @@ import { ErrorScene, ErrorSceneName } from "../Reconnecting/ErrorScene";
|
||||
import { WAError } from "../Reconnecting/WAError";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
||||
import { translator } from "../../Translator/Translator";
|
||||
|
||||
export const EntrySceneName = "EntryScene";
|
||||
|
||||
@ -12,6 +13,7 @@ export const EntrySceneName = "EntryScene";
|
||||
* and to route to the next correct scene.
|
||||
*/
|
||||
export class EntryScene extends Scene {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: EntrySceneName,
|
||||
@ -24,41 +26,50 @@ export class EntryScene extends Scene {
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(ReconnectingTextures.mainFont, "resources/fonts/arcade.png", "resources/fonts/arcade.xml");
|
||||
this.load.spritesheet("cat", "resources/characters/pipoya/Cat 01-1.png", { frameWidth: 32, frameHeight: 32 });
|
||||
translator.loadCurrentLanguageFile(this.load);
|
||||
}
|
||||
|
||||
create() {
|
||||
gameManager
|
||||
.init(this.scene)
|
||||
.then((nextSceneName) => {
|
||||
// Let's rescale before starting the game
|
||||
// We can do it at this stage.
|
||||
waScaleManager.applyNewSize();
|
||||
this.scene.start(nextSceneName);
|
||||
translator
|
||||
.loadCurrentLanguageObject(this.cache)
|
||||
.catch((e: unknown) => {
|
||||
console.error("Error during language loading!", e);
|
||||
throw e;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response && err.response.status == 404) {
|
||||
ErrorScene.showError(
|
||||
new WAError(
|
||||
"Access link incorrect",
|
||||
"Could not find map. Please check your access link.",
|
||||
"If you want more information, you may contact administrator or contact us at: hello@workadventu.re"
|
||||
),
|
||||
this.scene
|
||||
);
|
||||
} else if (err.response && err.response.status == 403) {
|
||||
ErrorScene.showError(
|
||||
new WAError(
|
||||
"Connection rejected",
|
||||
"You cannot join the World. Try again later" +
|
||||
(err.response.data ? ". \n\r \n\r" + `${err.response.data}` : "") +
|
||||
".",
|
||||
"If you want more information, you may contact administrator or contact us at: hello@workadventu.re"
|
||||
),
|
||||
this.scene
|
||||
);
|
||||
} else {
|
||||
ErrorScene.showError(err, this.scene);
|
||||
}
|
||||
.finally(() => {
|
||||
gameManager
|
||||
.init(this.scene)
|
||||
.then((nextSceneName) => {
|
||||
// Let's rescale before starting the game
|
||||
// We can do it at this stage.
|
||||
waScaleManager.applyNewSize();
|
||||
this.scene.start(nextSceneName);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response && err.response.status == 404) {
|
||||
ErrorScene.showError(
|
||||
new WAError(
|
||||
"Access link incorrect",
|
||||
"Could not find map. Please check your access link.",
|
||||
"If you want more information, you may contact administrator or contact us at: hello@workadventu.re"
|
||||
),
|
||||
this.scene
|
||||
);
|
||||
} else if (err.response && err.response.status == 403) {
|
||||
ErrorScene.showError(
|
||||
new WAError(
|
||||
"Connection rejected",
|
||||
"You cannot join the World. Try again later" +
|
||||
(err.response.data ? ". \n\r \n\r" + `${err.response.data}` : "") +
|
||||
".",
|
||||
"If you want more information, you may contact administrator or contact us at: hello@workadventu.re"
|
||||
),
|
||||
this.scene
|
||||
);
|
||||
} else {
|
||||
ErrorScene.showError(err, this.scene);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
72
front/src/Translator/TranslationCompiler.ts
Normal file
72
front/src/Translator/TranslationCompiler.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import fs from "fs";
|
||||
|
||||
const translationsBasePath = "./translations";
|
||||
const fallbackLanguage = process.env.FALLBACK_LANGUAGE || "en-US";
|
||||
|
||||
export type LanguageFound = {
|
||||
id: string;
|
||||
default: boolean;
|
||||
};
|
||||
|
||||
const getAllLanguagesByFiles = (dirPath: string, languages: Array<LanguageFound> | undefined) => {
|
||||
const files = fs.readdirSync(dirPath);
|
||||
languages = languages || new Array<LanguageFound>();
|
||||
|
||||
files.forEach(function (file) {
|
||||
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
|
||||
languages = getAllLanguagesByFiles(dirPath + "/" + file, languages);
|
||||
} else {
|
||||
const parts = file.split(".");
|
||||
|
||||
if (parts.length !== 3 || parts[0] !== "index" || parts[2] !== "json") {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawData = fs.readFileSync(dirPath + "/" + file, "utf-8");
|
||||
const languageObject = JSON.parse(rawData);
|
||||
|
||||
languages?.push({
|
||||
id: parts[1],
|
||||
default: languageObject.default !== undefined && languageObject.default,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return languages;
|
||||
};
|
||||
|
||||
const getFallbackLanguageObject = (dirPath: string, languageObject: Object | undefined) => {
|
||||
const files = fs.readdirSync(dirPath);
|
||||
languageObject = languageObject || {};
|
||||
|
||||
files.forEach(function (file) {
|
||||
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
|
||||
languageObject = getFallbackLanguageObject(dirPath + "/" + file, languageObject);
|
||||
} else {
|
||||
const parts = file.split(".");
|
||||
|
||||
if (parts.length !== 3 || parts[1] !== fallbackLanguage || parts[2] !== "json") {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawData = fs.readFileSync(dirPath + "/" + file, "utf-8");
|
||||
languageObject = { ...languageObject, ...JSON.parse(rawData) };
|
||||
}
|
||||
});
|
||||
|
||||
return languageObject;
|
||||
};
|
||||
|
||||
const languagesToObject = () => {
|
||||
const object: { [key: string]: boolean } = {};
|
||||
|
||||
languages.forEach((language) => {
|
||||
object[language.id] = false;
|
||||
});
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
export const languages = getAllLanguagesByFiles(translationsBasePath, undefined);
|
||||
export const languagesObject = languagesToObject();
|
||||
export const fallbackLanguageObject = getFallbackLanguageObject(translationsBasePath, undefined);
|
170
front/src/Translator/Translator.ts
Normal file
170
front/src/Translator/Translator.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import { FALLBACK_LANGUAGE } from "../Enum/EnvironmentVariable";
|
||||
import { getCookie } from "../Utils/Cookies";
|
||||
|
||||
export type Language = {
|
||||
language: string;
|
||||
country: string;
|
||||
};
|
||||
|
||||
type LanguageObject = {
|
||||
[key: string]: string | LanguageObject;
|
||||
};
|
||||
|
||||
class Translator {
|
||||
public readonly fallbackLanguage: Language = this.getLanguageByString(FALLBACK_LANGUAGE) || {
|
||||
language: "en",
|
||||
country: "US",
|
||||
};
|
||||
|
||||
private readonly fallbackLanguageObject: LanguageObject = FALLBACK_LANGUAGE_OBJECT as LanguageObject;
|
||||
|
||||
private currentLanguage: Language;
|
||||
private currentLanguageObject: LanguageObject;
|
||||
|
||||
public constructor() {
|
||||
this.currentLanguage = this.fallbackLanguage;
|
||||
this.currentLanguageObject = this.fallbackLanguageObject;
|
||||
|
||||
this.defineCurrentLanguage();
|
||||
}
|
||||
|
||||
public getLanguageByString(languageString: string): Language | undefined {
|
||||
const parts = languageString.split("-");
|
||||
if (parts.length !== 2 || parts[0].length !== 2 || parts[1].length !== 2) {
|
||||
console.error(`Language string "${languageString}" do not respect RFC 5646 with language and country code`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
language: parts[0].toLowerCase(),
|
||||
country: parts[1].toUpperCase(),
|
||||
};
|
||||
}
|
||||
|
||||
public getStringByLanguage(language: Language): string | undefined {
|
||||
return `${language.language}-${language.country}`;
|
||||
}
|
||||
|
||||
public loadCurrentLanguageFile(pluginLoader: Phaser.Loader.LoaderPlugin) {
|
||||
const languageString = this.getStringByLanguage(this.currentLanguage);
|
||||
pluginLoader.json({
|
||||
key: `language-${languageString}`,
|
||||
url: `resources/translations/${languageString}.json`,
|
||||
});
|
||||
}
|
||||
|
||||
public loadCurrentLanguageObject(cacheManager: Phaser.Cache.CacheManager): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const languageObject: Object = cacheManager.json.get(
|
||||
`language-${this.getStringByLanguage(this.currentLanguage)}`
|
||||
);
|
||||
|
||||
if (!languageObject) {
|
||||
return reject();
|
||||
}
|
||||
|
||||
this.currentLanguageObject = languageObject as LanguageObject;
|
||||
return resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public getLanguageWithoutCountry(languageString: string): Language | undefined {
|
||||
if (languageString.length !== 2) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let languageFound = undefined;
|
||||
|
||||
const languages: { [key: string]: boolean } = LANGUAGES as { [key: string]: boolean };
|
||||
|
||||
for (const language in languages) {
|
||||
if (language.startsWith(languageString) && languages[language]) {
|
||||
languageFound = this.getLanguageByString(language);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return languageFound;
|
||||
}
|
||||
|
||||
private defineCurrentLanguage() {
|
||||
const navigatorLanguage: string | undefined = navigator.language;
|
||||
const cookieLanguage = getCookie("language");
|
||||
let currentLanguage = undefined;
|
||||
|
||||
if (cookieLanguage && typeof cookieLanguage === "string") {
|
||||
const cookieLanguageObject = this.getLanguageByString(cookieLanguage);
|
||||
if (cookieLanguageObject) {
|
||||
currentLanguage = cookieLanguageObject;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentLanguage && navigatorLanguage) {
|
||||
const navigatorLanguageObject =
|
||||
navigator.language.length === 2
|
||||
? this.getLanguageWithoutCountry(navigatorLanguage)
|
||||
: this.getLanguageByString(navigatorLanguage);
|
||||
if (navigatorLanguageObject) {
|
||||
currentLanguage = navigatorLanguageObject;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentLanguage || currentLanguage === this.fallbackLanguage) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentLanguage = currentLanguage;
|
||||
}
|
||||
|
||||
private getObjectValueByPath(path: string, object: LanguageObject): string | undefined {
|
||||
const paths = path.split(".");
|
||||
let currentValue: LanguageObject | string = object;
|
||||
|
||||
for (const path of paths) {
|
||||
if (typeof currentValue === "string" || currentValue[path] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
currentValue = currentValue[path];
|
||||
}
|
||||
|
||||
if (typeof currentValue !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
private formatStringWithParams(string: string, params: { [key: string]: string | number }): string {
|
||||
let formattedString = string;
|
||||
|
||||
for (const param in params) {
|
||||
const regex = `/{{\\s*\\${param}\\s*}}/g`;
|
||||
formattedString = formattedString.replace(new RegExp(regex), params[param].toString());
|
||||
}
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
public _(key: string, params?: { [key: string]: string | number }): string {
|
||||
const currentLanguageValue = this.getObjectValueByPath(key, this.currentLanguageObject);
|
||||
|
||||
if (currentLanguageValue) {
|
||||
return params ? this.formatStringWithParams(currentLanguageValue, params) : currentLanguageValue;
|
||||
}
|
||||
|
||||
console.warn(`"${key}" key cannot be found in ${this.getStringByLanguage(this.currentLanguage)} language`);
|
||||
|
||||
const fallbackLanguageValue = this.getObjectValueByPath(key, this.fallbackLanguageObject);
|
||||
|
||||
if (fallbackLanguageValue) {
|
||||
return params ? this.formatStringWithParams(fallbackLanguageValue, params) : fallbackLanguageValue;
|
||||
}
|
||||
|
||||
console.warn(`"${key}" key cannot be found in ${this.getStringByLanguage(this.fallbackLanguage)} fallback language`);
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
export const translator = new Translator();
|
20
front/src/Utils/Cookies.ts
Normal file
20
front/src/Utils/Cookies.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export const setCookie = (name: string, value: unknown, days: number) => {
|
||||
let expires = "";
|
||||
if (days) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
};
|
||||
|
||||
export const getCookie = (name: string): unknown | undefined => {
|
||||
const nameEquals = name + "=";
|
||||
const ca = document.cookie.split(";");
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == " ") c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEquals) == 0) return c.substring(nameEquals.length, c.length);
|
||||
}
|
||||
return undefined;
|
||||
};
|
2
front/src/define-plugin.d.ts
vendored
Normal file
2
front/src/define-plugin.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
declare const FALLBACK_LANGUAGE_OBJECT: Object;
|
||||
declare const LANGUAGES: Object;
|
5
front/translations/en-US/index.en-US.json
Normal file
5
front/translations/en-US/index.en-US.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"language": "English",
|
||||
"country": "United States",
|
||||
"default": true
|
||||
}
|
5
front/translations/en-US/test.en-US.json
Normal file
5
front/translations/en-US/test.en-US.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"test": {
|
||||
"nolway": "Too mutch cofee"
|
||||
}
|
||||
}
|
5
front/translations/fr-FR/index.fr-FR.json
Normal file
5
front/translations/fr-FR/index.fr-FR.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"language": "Français",
|
||||
"country": "France",
|
||||
"default": true
|
||||
}
|
5
front/translations/fr-FR/test.fr-FR.json
Normal file
5
front/translations/fr-FR/test.fr-FR.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"test": {
|
||||
"nolway": "Trop de café"
|
||||
}
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
import type { Configuration } from "webpack";
|
||||
import type WebpackDevServer from "webpack-dev-server";
|
||||
import path from "path";
|
||||
import webpack from "webpack";
|
||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
||||
import path from "path";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import type { Configuration } from "webpack";
|
||||
import webpack from "webpack";
|
||||
import type WebpackDevServer from "webpack-dev-server";
|
||||
import type { LanguageFound } from "./src/Translator/TranslationCompiler";
|
||||
import { fallbackLanguageObject, languages, languagesObject } from "./src/Translator/TranslationCompiler";
|
||||
|
||||
const MergeJsonWebpackPlugin = require("merge-jsons-webpack-plugin");
|
||||
|
||||
const mode = process.env.NODE_ENV ?? "development";
|
||||
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||
@ -141,6 +145,11 @@ module.exports = {
|
||||
filename: "fonts/[name][ext]",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
exclude: /node_modules/,
|
||||
type: "asset",
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
@ -210,6 +219,24 @@ module.exports = {
|
||||
NODE_ENV: mode,
|
||||
DISABLE_ANONYMOUS: false,
|
||||
OPID_LOGIN_SCREEN_PROVIDER: null,
|
||||
FALLBACK_LANGUAGE: null,
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
FALLBACK_LANGUAGE_OBJECT: JSON.stringify(fallbackLanguageObject),
|
||||
LANGUAGES: JSON.stringify(languagesObject),
|
||||
}),
|
||||
new MergeJsonWebpackPlugin({
|
||||
output: {
|
||||
groupBy: languages.map((language: LanguageFound) => {
|
||||
return {
|
||||
pattern: `./translations/**/*.${language.id}.json`,
|
||||
fileName: `./resources/translations/${language.id}.json`
|
||||
};
|
||||
})
|
||||
},
|
||||
globOptions: {
|
||||
nosort: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
} as Configuration & WebpackDevServer.Configuration;
|
||||
|
@ -2776,6 +2776,18 @@ glob-to-regexp@^0.4.1:
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||
|
||||
glob@7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
|
||||
integrity sha1-gFIR3wT6rxxjo2ADBs31reULLsg=
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.2"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.0.3, glob@^7.1.3, glob@^7.1.6:
|
||||
version "7.1.7"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
|
||||
@ -3860,6 +3872,13 @@ merge-descriptors@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||
|
||||
merge-jsons-webpack-plugin@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/merge-jsons-webpack-plugin/-/merge-jsons-webpack-plugin-2.0.1.tgz#f2975ce0f734171331d42eee62d63329031800b4"
|
||||
integrity sha512-8GP8rpOX3HSFsm7Gx+b3OAQR7yhgeAQvMqcZOJ+/cQIrqdak1c42a2T2vyeee8pzGPBf7pMLumthPh4CHgv2BA==
|
||||
dependencies:
|
||||
glob "7.1.1"
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
@ -3961,7 +3980,7 @@ minimalistic-crypto-utils@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
|
||||
|
||||
minimatch@^3.0.4:
|
||||
minimatch@^3.0.2, minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
|
Loading…
Reference in New Issue
Block a user