Compare commits
25 Commits
feature/im
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e67ca3794 | ||
|
|
ceebf6dbe1 | ||
|
|
8d4f9fd953 | ||
|
|
1c3032068e | ||
|
|
7b4b72166f | ||
|
|
707e5bb8b0 | ||
|
|
ad5d364c57 | ||
|
|
138fadf672 | ||
|
|
82eabc41fe | ||
|
|
3e6aab6b00 | ||
|
|
5396b3f1fb | ||
|
|
b1ba887922 | ||
|
|
93a1c593fe | ||
|
|
b7044248cb | ||
|
|
ea5e2f660b | ||
|
|
e9779a3d2f | ||
|
|
1c6ec0f8e3 | ||
|
|
84bd81e525 | ||
|
|
a84cbee9db | ||
|
|
97b16c8c0c | ||
|
|
0a6a35b022 | ||
|
|
6565b4101b | ||
|
|
53dc50c0dd | ||
|
|
2a59faec17 | ||
|
|
00534f7edd |
2
.github/workflows/build-app-pro-beta.yaml
vendored
2
.github/workflows/build-app-pro-beta.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
|
||||
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
2
.github/workflows/build-app-pro.yaml
vendored
2
.github/workflows/build-app-pro.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
|
||||
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
2
.github/workflows/build-cloud-pro.yaml
vendored
2
.github/workflows/build-cloud-pro.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
|
||||
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
2
.github/workflows/build-docker-pro.yaml
vendored
2
.github/workflows/build-docker-pro.yaml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
|
||||
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
2
.github/workflows/build-npm-pro.yaml
vendored
2
.github/workflows/build-npm-pro.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
|
||||
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
2
.github/workflows/e2e-pro.yaml
vendored
2
.github/workflows/e2e-pro.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
|
||||
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,4 +32,5 @@ packages/api/src/packagedPluginsContent.js
|
||||
.VSCodeCounter
|
||||
|
||||
packages/web/public/*.html
|
||||
e2e-tests/screenshots/*.png
|
||||
e2e-tests/screenshots/*.png
|
||||
.aider*
|
||||
|
||||
43
common/translations-cli/addMissing.js
Normal file
43
common/translations-cli/addMissing.js
Normal file
@@ -0,0 +1,43 @@
|
||||
//@ts-check
|
||||
const { getDefaultTranslations, getLanguageTranslations } = require('./helpers');
|
||||
|
||||
/**
|
||||
* @param {string} language
|
||||
*/
|
||||
function getMissingTranslations(language) {
|
||||
const source = getDefaultTranslations();
|
||||
/** @type {Record<string, string>} */
|
||||
let target;
|
||||
|
||||
try {
|
||||
target = getLanguageTranslations(language);
|
||||
} catch {
|
||||
console.log(`Language ${language} not found, creating a new one`);
|
||||
target = {};
|
||||
}
|
||||
|
||||
let added = 0;
|
||||
let removed = 0;
|
||||
|
||||
for (const key in source) {
|
||||
if (!target[key]) {
|
||||
target[key] = `*** ${source[key]}`;
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in target) {
|
||||
if (!source[key]) {
|
||||
delete target[key];
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
const newLength = Object.keys(target).length;
|
||||
|
||||
return { result: target, stats: { added, removed, newLength } };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMissingTranslations,
|
||||
};
|
||||
16
common/translations-cli/constants.js
Normal file
16
common/translations-cli/constants.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// @ts-check
|
||||
//
|
||||
const defaultLanguage = 'en';
|
||||
|
||||
/** @typedef {{ extensions: string[], directories: string[] }} ExtractConfig
|
||||
|
||||
/** @type {ExtractConfig} */
|
||||
const defaultExtractConfig = {
|
||||
extensions: ['.js', '.ts', '.svelte'],
|
||||
directories: ['app', 'packages/web'],
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
defaultLanguage,
|
||||
defaultExtractConfig,
|
||||
};
|
||||
84
common/translations-cli/extract.js
Normal file
84
common/translations-cli/extract.js
Normal file
@@ -0,0 +1,84 @@
|
||||
//@ts-check
|
||||
const fs = require('fs');
|
||||
const { promisify } = require('util');
|
||||
|
||||
const { getFiles } = require('./helpers');
|
||||
|
||||
const readFilePromise = promisify(fs.readFile);
|
||||
|
||||
const translationRegex = /_t\(\s*['"]([^'"]+)['"]\s*,\s*\{\s*defaultMessage\s*:\s*['"]([^'"]+)['"]\s*\}/g;
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
*
|
||||
* @returns {Promise<Record<string, string>>}
|
||||
*/
|
||||
async function extractTranslationsFromFile(file) {
|
||||
/** @type {Record<string, string>} */
|
||||
const translations = {};
|
||||
const content = await readFilePromise(file, 'utf-8');
|
||||
let match;
|
||||
|
||||
while ((match = translationRegex.exec(content)) !== null) {
|
||||
const [_, key, defaultText] = match;
|
||||
translations[key] = defaultText;
|
||||
}
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
/** @typedef {{ ignoreDuplicates?: boolean }} ExtractOptions */
|
||||
|
||||
/**
|
||||
* @param {string[]} directories
|
||||
* @param {string[]} extensions
|
||||
* @param {ExtractOptions} options
|
||||
*
|
||||
* @returns {Promise<Record<string, string>>}
|
||||
*/
|
||||
async function extractAllTranslations(directories, extensions, options = {}) {
|
||||
const { ignoreDuplicates } = options;
|
||||
|
||||
try {
|
||||
/** @type {Record<string, string>} */
|
||||
const allTranslations = {};
|
||||
/** @type {Record<string, string[]>} */
|
||||
const translationKeyToFiles = {};
|
||||
|
||||
for (const dir of directories) {
|
||||
const files = await getFiles(dir, extensions);
|
||||
|
||||
for (const file of files) {
|
||||
const fileTranslations = await extractTranslationsFromFile(file);
|
||||
|
||||
for (const key in fileTranslations) {
|
||||
if (!translationKeyToFiles[key]) {
|
||||
translationKeyToFiles[key] = [];
|
||||
}
|
||||
|
||||
translationKeyToFiles[key].push(file);
|
||||
|
||||
if (!ignoreDuplicates && allTranslations[key] && allTranslations[key] !== fileTranslations[key]) {
|
||||
console.error(
|
||||
`Different translations for the same key [${key}] found. ${file}: ${
|
||||
fileTranslations[key]
|
||||
}. Previous value: ${allTranslations[key]} was found in ${translationKeyToFiles[key].join(', ')}`
|
||||
);
|
||||
throw new Error(`Duplicate translation key found: ${key}`);
|
||||
}
|
||||
|
||||
allTranslations[key] = fileTranslations[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allTranslations;
|
||||
} catch (error) {
|
||||
console.error('Error extracting translations:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
extractTranslationsFromFile,
|
||||
extractAllTranslations,
|
||||
};
|
||||
194
common/translations-cli/helpers.js
Normal file
194
common/translations-cli/helpers.js
Normal file
@@ -0,0 +1,194 @@
|
||||
//@ts-check
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { defaultLanguage } = require('./constants');
|
||||
const sortJsonKeysAlphabetically = require('./sortJsonKeysAlphabetically');
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {string[]} extensions
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasValidExtension(file, extensions) {
|
||||
return extensions.includes(path.extname(file).toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @param {string[]} extensions
|
||||
*
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async function getFiles(dir, extensions) {
|
||||
const files = await fs.promises.readdir(dir);
|
||||
const allFiles = await Promise.all(
|
||||
files.map(async file => {
|
||||
const filePath = path.join(dir, file);
|
||||
const stats = await fs.promises.stat(filePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return getFiles(filePath, extensions);
|
||||
} else if (stats.isFile() && hasValidExtension(file, extensions)) {
|
||||
return filePath;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
const validFiles = /** @type {string[]} */ (allFiles.flat().filter(file => file !== null));
|
||||
|
||||
return validFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | string[]} value
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatDefaultValue(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(', ');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const scriptDir = getScriptDir();
|
||||
/** @param {string} file
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function resolveFile(file) {
|
||||
if (path.isAbsolute(file)) {
|
||||
return file;
|
||||
}
|
||||
|
||||
return path.resolve(scriptDir, '..', '..', file);
|
||||
}
|
||||
|
||||
/** @param {string[]} dirs
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function resolveDirs(dirs) {
|
||||
return dirs.map(resolveFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} extensions
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function resolveExtensions(extensions) {
|
||||
return extensions.map(ext => (ext.startsWith('.') ? ext : `.${ext}`));
|
||||
}
|
||||
|
||||
function getScriptDir() {
|
||||
if (require.main?.filename) {
|
||||
return path.dirname(require.main.filename);
|
||||
}
|
||||
|
||||
if ('pkg' in process && process.pkg) {
|
||||
return path.dirname(process.execPath);
|
||||
}
|
||||
|
||||
return __dirname;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
*/
|
||||
function ensureFileDirExists(file) {
|
||||
const dir = path.dirname(file);
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, string>} existingTranslations - Previously extracted translations
|
||||
* @param {Record<string, string>} newTranslations - Newly extracted translations
|
||||
* @returns {{ added: string[], removed: string[], updated: string[] }} Translation changes
|
||||
*/
|
||||
const getTranslationChanges = (existingTranslations, newTranslations) => {
|
||||
const existingKeys = new Set(Object.keys(existingTranslations || {}));
|
||||
const newKeys = new Set(Object.keys(newTranslations));
|
||||
|
||||
const added = [...newKeys].filter(key => !existingKeys.has(key));
|
||||
const removed = [...existingKeys].filter(key => !newKeys.has(key));
|
||||
const updated = [...newKeys].filter(
|
||||
key => existingKeys.has(key) && existingTranslations[key] !== newTranslations[key]
|
||||
);
|
||||
|
||||
return { added, removed, updated };
|
||||
};
|
||||
|
||||
function getDefaultTranslations() {
|
||||
return getLanguageTranslations(defaultLanguage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} language
|
||||
*
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function getLanguageTranslations(language) {
|
||||
const file = resolveFile(`translations/${language}.json`);
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} language
|
||||
* @param {Record<string, string>} translations
|
||||
*/
|
||||
function setLanguageTranslations(language, translations) {
|
||||
const file = resolveFile(`translations/${language}.json`);
|
||||
const sorted = sortJsonKeysAlphabetically(translations);
|
||||
|
||||
fs.writeFileSync(file, JSON.stringify(sorted, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} language
|
||||
* @param {Record<string, string>} newTranslations
|
||||
*/
|
||||
function updateLanguageTranslations(language, newTranslations) {
|
||||
const translations = getLanguageTranslations(language);
|
||||
const updatedTranslations = { ...translations, ...newTranslations };
|
||||
const sorted = sortJsonKeysAlphabetically(updatedTranslations);
|
||||
|
||||
setLanguageTranslations(language, sorted);
|
||||
}
|
||||
|
||||
function getAllLanguages() {
|
||||
const dir = resolveFile('translations');
|
||||
|
||||
const files = fs.readdirSync(dir);
|
||||
const languages = files.filter(file => file.endsWith('.json')).map(file => file.replace('.json', ''));
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
function getAllNonDefaultLanguages() {
|
||||
return getAllLanguages().filter(language => language !== defaultLanguage);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hasValidExtension,
|
||||
getFiles,
|
||||
formatDefaultValue,
|
||||
resolveFile,
|
||||
resolveDirs,
|
||||
resolveExtensions,
|
||||
ensureFileDirExists,
|
||||
getTranslationChanges,
|
||||
getDefaultTranslations,
|
||||
getLanguageTranslations,
|
||||
setLanguageTranslations,
|
||||
updateLanguageTranslations,
|
||||
getAllLanguages,
|
||||
getAllNonDefaultLanguages,
|
||||
};
|
||||
3
common/translations-cli/index.js
Normal file
3
common/translations-cli/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { program } = require('./program');
|
||||
|
||||
program.parse();
|
||||
163
common/translations-cli/program.js
Normal file
163
common/translations-cli/program.js
Normal file
@@ -0,0 +1,163 @@
|
||||
//@ts-check
|
||||
const fs = require('fs');
|
||||
const { program } = require('commander');
|
||||
const {
|
||||
resolveDirs,
|
||||
resolveExtensions,
|
||||
getTranslationChanges,
|
||||
setLanguageTranslations,
|
||||
getAllNonDefaultLanguages,
|
||||
updateLanguageTranslations,
|
||||
getDefaultTranslations,
|
||||
} = require('./helpers');
|
||||
const { extractAllTranslations } = require('./extract');
|
||||
const { getMissingTranslations } = require('./addMissing');
|
||||
const { defaultLanguage, defaultExtractConfig } = require('./constants');
|
||||
const { removeUnusedAllTranslations, removeUnusedForSignelLanguage } = require('./removeUnused');
|
||||
|
||||
/**
|
||||
* @typedef {import('./constants').ExtractConfig & { verbose?: boolean, ignoreUnused?: boolean }} ExtractOptions
|
||||
*/
|
||||
|
||||
program.name('dbgate-translations-cli').description('CLI tool for managing translation').version('1.0.0');
|
||||
|
||||
program
|
||||
.command('extract')
|
||||
.description('Extract translation keys from source files')
|
||||
.option('-d, --directories <directories...>', 'directories to search', defaultExtractConfig.directories)
|
||||
.option('-e, --extensions <extensions...>', 'file extensions to process', defaultExtractConfig.extensions)
|
||||
.option('-r, --ignoreUnused', 'Ignore unused keys in the output file')
|
||||
.option('-v, --verbose', 'verbose mode')
|
||||
.action(async (/** @type {ExtractOptions} */ options) => {
|
||||
try {
|
||||
const { directories, extensions, verbose, ignoreUnused } = options;
|
||||
|
||||
const resolvedRirectories = resolveDirs(directories);
|
||||
const resolvedExtensions = resolveExtensions(extensions);
|
||||
|
||||
const extractedTranslations = await extractAllTranslations(resolvedRirectories, resolvedExtensions);
|
||||
const defaultTranslations = getDefaultTranslations();
|
||||
|
||||
const { added, removed, updated } = getTranslationChanges(defaultTranslations, extractedTranslations);
|
||||
|
||||
console.log('\nTranslation changes:');
|
||||
console.log(`- Added: ${added.length} keys`);
|
||||
console.log(`- ${ignoreUnused ? 'Unused' : 'Removed'}: ${removed.length} keys`);
|
||||
console.log(`- Updated: ${updated.length} keys`);
|
||||
console.log(`- Total: ${Object.keys(extractedTranslations).length} keys`);
|
||||
|
||||
if (verbose) {
|
||||
if (added.length > 0) {
|
||||
console.log('\nNew keys:');
|
||||
added.forEach(key => console.log(` + ${key}`));
|
||||
}
|
||||
|
||||
if (removed.length > 0) {
|
||||
console.log('\nRemoved keys:');
|
||||
removed.forEach(key => console.log(` - ${key}`));
|
||||
}
|
||||
|
||||
if (updated.length > 0) {
|
||||
console.log('\nUpdated keys:');
|
||||
updated.forEach(key => {
|
||||
console.log(` ~ ${key}`);
|
||||
console.log(` Old: ${defaultLanguage[key]}`);
|
||||
console.log(` New: ${extractedTranslations[key]}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ignoreUnused) {
|
||||
console.log('New translations were saved. Unused keys are kept.\n');
|
||||
updateLanguageTranslations(defaultLanguage, extractedTranslations);
|
||||
|
||||
if (verbose) {
|
||||
console.log('\nUnused keys:');
|
||||
for (const key of removed) {
|
||||
console.log(`${key}: "${defaultTranslations[key]}"`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('Unused keys were removed.\n');
|
||||
setLanguageTranslations(defaultLanguage, extractedTranslations);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('Error during extraction:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
const ALL_LANGUAGES = 'all';
|
||||
|
||||
/**
|
||||
* @param {string} target
|
||||
*/
|
||||
function addMissingTranslations(target) {
|
||||
console.log(`Adding missing keys for language: ${target}`);
|
||||
const { result, stats } = getMissingTranslations(target);
|
||||
console.log(`Added: ${stats.added}, Removed: ${stats.removed}, Total: ${stats.newLength}`);
|
||||
setLanguageTranslations(target, result);
|
||||
console.log(`New translations for ${target} were saved.`);
|
||||
}
|
||||
|
||||
program
|
||||
.command('add-missing')
|
||||
.description('Add missing keys for a langauge to the translation file')
|
||||
.option('-t, --target <target>', 'language to add missing translations to', ALL_LANGUAGES)
|
||||
.action(options => {
|
||||
try {
|
||||
const { target } = options;
|
||||
const languages = getAllNonDefaultLanguages();
|
||||
|
||||
if (target === ALL_LANGUAGES) {
|
||||
console.log('Adding missing keys for all languages\n');
|
||||
for (const language of languages) {
|
||||
addMissingTranslations(language);
|
||||
console.log();
|
||||
}
|
||||
} else {
|
||||
addMissingTranslations(target);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('Error during add-missing:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('remove-unused')
|
||||
.description('Remove unused keys from the translation files')
|
||||
.option('-t, --target <target>', 'language to add missing translations to', ALL_LANGUAGES)
|
||||
.action(async options => {
|
||||
try {
|
||||
const { target } = options;
|
||||
if (target === ALL_LANGUAGES) {
|
||||
console.log('Removing unused keys from all languages\n');
|
||||
await removeUnusedAllTranslations();
|
||||
} else {
|
||||
await removeUnusedForSignelLanguage(target);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('Error during add-missing:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('check')
|
||||
.description('Check if there are multiple default values for the same key')
|
||||
.action(async () => {
|
||||
try {
|
||||
await extractAllTranslations(defaultExtractConfig.directories, defaultExtractConfig.extensions);
|
||||
console.log('No problems found while extracting translations.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('Error during check:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = { program };
|
||||
46
common/translations-cli/removeUnused.js
Normal file
46
common/translations-cli/removeUnused.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// @ts-check
|
||||
const { defaultExtractConfig } = require('./constants');
|
||||
const { extractAllTranslations } = require('./extract');
|
||||
const { getLanguageTranslations, getAllLanguages, setLanguageTranslations } = require('./helpers');
|
||||
|
||||
const { directories, extensions } = defaultExtractConfig;
|
||||
|
||||
/**
|
||||
* @param {string} language
|
||||
* @param {Record<string, string>} source
|
||||
*/
|
||||
function getUsedTranslations(language, source) {
|
||||
const languageTranslations = getLanguageTranslations(language);
|
||||
|
||||
for (const key in languageTranslations) {
|
||||
if (!(key in source)) {
|
||||
delete languageTranslations[key];
|
||||
}
|
||||
}
|
||||
|
||||
return languageTranslations;
|
||||
}
|
||||
|
||||
async function removeUnusedAllTranslations() {
|
||||
const source = await extractAllTranslations(directories, extensions);
|
||||
const languages = getAllLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
const newTranslations = getUsedTranslations(language, source);
|
||||
setLanguageTranslations(language, newTranslations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} language
|
||||
*/
|
||||
async function removeUnusedForSignelLanguage(language) {
|
||||
const source = await extractAllTranslations(directories, extensions);
|
||||
const newTranslations = getUsedTranslations(language, source);
|
||||
setLanguageTranslations(language, newTranslations);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
removeUnusedAllTranslations,
|
||||
removeUnusedForSignelLanguage,
|
||||
};
|
||||
24
common/translations-cli/sortJsonKeysAlphabetically.js
Normal file
24
common/translations-cli/sortJsonKeysAlphabetically.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {object|string} json
|
||||
* @returns {object}
|
||||
*/
|
||||
function sortJsonKeysAlphabetically(json) {
|
||||
const obj = typeof json === 'string' ? JSON.parse(json) : json;
|
||||
|
||||
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const sortedObj = Object.keys(obj)
|
||||
.sort()
|
||||
.reduce((result, key) => {
|
||||
result[key] = obj[key];
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
return sortedObj;
|
||||
}
|
||||
|
||||
module.exports = sortJsonKeysAlphabetically;
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
restart: always
|
||||
ports:
|
||||
- 16005:3306
|
||||
- "16014:22"
|
||||
- "16015:22"
|
||||
|
||||
mysql-ssh-keyfile:
|
||||
build: containers/mysql-ssh-keyfile
|
||||
@@ -54,7 +54,7 @@ services:
|
||||
image: mcr.microsoft.com/mssql/server
|
||||
restart: always
|
||||
ports:
|
||||
- 16012:1433
|
||||
- 16014:1433
|
||||
environment:
|
||||
- ACCEPT_EULA=Y
|
||||
- SA_PASSWORD=Pwd2020Db
|
||||
|
||||
@@ -67,7 +67,11 @@
|
||||
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn build:plugins:frontend",
|
||||
"dbgate-serve": "node packages/dbgate/bin/dbgate-serve.js",
|
||||
"workflows": "node common/processWorkflows.js",
|
||||
"cy:open": "cd e2e-tests && yarn cy:open"
|
||||
"cy:open": "cd e2e-tests && yarn cy:open",
|
||||
"translations:extract": "node common/translations-cli/index.js extract",
|
||||
"translations:add-missing": "node common/translations-cli/index.js add-missing",
|
||||
"translations:remove-unused": "node common/translations-cli/index.js remove-unused",
|
||||
"translations:check": "node common/translations-cli/index.js check"
|
||||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^5.1.0",
|
||||
|
||||
@@ -77,6 +77,7 @@ function getPortalCollections() {
|
||||
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
|
||||
parent: process.env[`PARENT_${id}`] || undefined,
|
||||
useSeparateSchemas: !!process.env[`USE_SEPARATE_SCHEMAS_${id}`],
|
||||
localDataCenter: process.env[`LOCAL_DATA_CENTER_${id}`],
|
||||
|
||||
// SSH tunnel
|
||||
useSshTunnel: process.env[`USE_SSH_${id}`],
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div>Loading DbGate App</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@ant-design/colors": "^5.0.0",
|
||||
"@mdi/font": "^7.1.96",
|
||||
"@rollup/plugin-commonjs": "^20.0.0",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.5",
|
||||
"@rollup/plugin-replace": "^3.0.0",
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
@@ -57,6 +58,7 @@
|
||||
"uuid": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@messageformat/core": "^3.4.0",
|
||||
"chartjs-plugin-zoom": "^1.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"debug": "^4.3.4",
|
||||
|
||||
@@ -8,6 +8,7 @@ import sveltePreprocess from 'svelte-preprocess';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import css from 'rollup-plugin-css-only';
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
@@ -121,6 +122,7 @@ export default [
|
||||
sourceMap: !production,
|
||||
inlineSources: !production,
|
||||
}),
|
||||
json(),
|
||||
|
||||
// In dev mode, call `npm run start` once
|
||||
// the bundle has been generated
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import SettingsListener from './utility/SettingsListener.svelte';
|
||||
import { handleAuthOnStartup } from './clientAuth';
|
||||
import { initializeAppUpdates } from './utility/appUpdate';
|
||||
import { _t } from './translations';
|
||||
|
||||
export let isAdminPage = false;
|
||||
|
||||
@@ -95,10 +96,13 @@
|
||||
{:else}
|
||||
<AppStartInfo
|
||||
message={$loadingPluginStore.loadingPackageName
|
||||
? `Loading plugin ${$loadingPluginStore.loadingPackageName} ...`
|
||||
: 'Preparing plugins ...'}
|
||||
? _t('app.loading_plugin', {
|
||||
defaultMessage: `Loading plugin {plugin} ...`,
|
||||
values: { plugin: $loadingPluginStore.loadingPackageName },
|
||||
})
|
||||
: _t('app.preparing_pluguns', { defaultMessage: 'Preparing plugins ...' })}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<AppStartInfo message="Starting DbGate" />
|
||||
<AppStartInfo message={_t('app.starting', { defaultMessage: 'Starting DbGate' })} />
|
||||
{/if}
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { getConnectionClickActionSetting } from '../settings/settingsTools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -324,7 +325,7 @@
|
||||
hasPermission(`dbops/query`) && { onClick: handleNewQuery, text: 'New Query (server)', isNewQuery: true },
|
||||
$openedConnections.includes(data._id) &&
|
||||
data.status && {
|
||||
text: 'Refresh',
|
||||
text: _t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
onClick: handleRefresh,
|
||||
},
|
||||
hasPermission(`dbops/createdb`) &&
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.refresh',
|
||||
category: 'Data grid',
|
||||
name: 'Refresh',
|
||||
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
keyText: 'F5 | CtrlOrCommand+R',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -28,7 +28,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.revertRowChanges',
|
||||
category: 'Data grid',
|
||||
name: 'Revert row changes',
|
||||
name: _t('command.data_grid.revert_row_changes', { defaultMessage: 'Revert row changes' }),
|
||||
keyText: 'CtrlOrCommand+U',
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.containsChanges,
|
||||
onClick: () => getCurrentDataGrid().revertRowChanges(),
|
||||
@@ -37,8 +37,8 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.revertAllChanges',
|
||||
category: 'Data grid',
|
||||
name: 'Revert all changes',
|
||||
toolbarName: 'Revert all',
|
||||
name: _t('command.data_grid.revert_all_changes.name', { defaultMessage: 'Revert all changes' }),
|
||||
toolbarName: _t('command.data_grid.revert_all_changes.toolbar_name', { defaultMessage: 'Revert all' }),
|
||||
icon: 'icon undo',
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.containsChanges,
|
||||
onClick: () => getCurrentDataGrid().revertAllChanges(),
|
||||
@@ -47,8 +47,8 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.deleteSelectedRows',
|
||||
category: 'Data grid',
|
||||
name: 'Delete selected rows',
|
||||
toolbarName: 'Delete row(s)',
|
||||
name: _t('command.datagrid.delete_selected_rows.name', { defaultMessage: 'Delete selected rows' }),
|
||||
toolbarName: _t('command.datagrid.delete_selected_rows.toolbar_name', { defaultMessage: 'Delete row(s)' }),
|
||||
keyText: isMac() ? 'Command+Backspace' : 'CtrlOrCommand+Delete',
|
||||
icon: 'icon minus',
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
|
||||
@@ -58,8 +58,8 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.insertNewRow',
|
||||
category: 'Data grid',
|
||||
name: 'Insert new row',
|
||||
toolbarName: 'New row',
|
||||
name: _t('command.datagrid.insert_new_row.name', { defaultMessage: 'Insert new row' }),
|
||||
toolbarName: _t('command.datagrid.insert_new_row.toolbar_name', { defaultMessage: 'New row' }),
|
||||
icon: 'icon add',
|
||||
keyText: isMac() ? 'Command+I' : 'Insert',
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
|
||||
@@ -69,8 +69,8 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.addNewColumn',
|
||||
category: 'Data grid',
|
||||
name: 'Add new column',
|
||||
toolbarName: 'New column',
|
||||
name: _t('command.datagrid.add_new_column.name', { defaultMessage: 'Add new column' }),
|
||||
toolbarName: _t('command.datagrid.add_new_column.toolbar_name', { defaultMessage: 'New column' }),
|
||||
icon: 'icon add-column',
|
||||
testEnabled: () => getCurrentDataGrid()?.addNewColumnEnabled(),
|
||||
onClick: () => getCurrentDataGrid().addNewColumn(),
|
||||
@@ -79,8 +79,8 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.cloneRows',
|
||||
category: 'Data grid',
|
||||
name: 'Clone rows',
|
||||
toolbarName: 'Clone row(s)',
|
||||
name: _t('command.datagrid.clone_rows.name', { defaultMessage: 'Clone rows' }),
|
||||
toolbarName: _t('command.datagrid.clone_rows.toolbar_name', { defaultMessage: 'Clone row(s)' }),
|
||||
keyText: 'CtrlOrCommand+Shift+C',
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
|
||||
onClick: () => getCurrentDataGrid().cloneRows(),
|
||||
@@ -89,7 +89,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.setNull',
|
||||
category: 'Data grid',
|
||||
name: 'Set NULL',
|
||||
name: _t('command.datagrid.set_null.name', { defaultMessage: 'Set NULL' }),
|
||||
keyText: 'CtrlOrCommand+0',
|
||||
testEnabled: () =>
|
||||
getCurrentDataGrid()?.getGrider()?.editable && !getCurrentDataGrid()?.getEditorTypes()?.supportFieldRemoval,
|
||||
@@ -99,7 +99,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.removeField',
|
||||
category: 'Data grid',
|
||||
name: 'Remove field',
|
||||
name: _t('command.datagrid.remove_field.name', { defaultMessage: 'Remove field' }),
|
||||
keyText: 'CtrlOrCommand+0',
|
||||
testEnabled: () =>
|
||||
getCurrentDataGrid()?.getGrider()?.editable && getCurrentDataGrid()?.getEditorTypes()?.supportFieldRemoval,
|
||||
@@ -109,7 +109,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.undo',
|
||||
category: 'Data grid',
|
||||
name: 'Undo',
|
||||
name: _t('command.datagrid.undo.name', { defaultMessage: 'Undo' }),
|
||||
group: 'undo',
|
||||
icon: 'icon undo',
|
||||
toolbar: true,
|
||||
@@ -121,7 +121,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.redo',
|
||||
category: 'Data grid',
|
||||
name: 'Redo',
|
||||
name: _t('command.datagrid.redo.name', { defaultMessage: 'Redo' }),
|
||||
group: 'redo',
|
||||
icon: 'icon redo',
|
||||
toolbar: true,
|
||||
@@ -133,7 +133,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.reconnect',
|
||||
category: 'Data grid',
|
||||
name: 'Reconnect',
|
||||
name: _t('command.datagrid.reconnect.name', { defaultMessage: 'Reconnect' }),
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
onClick: () => getCurrentDataGrid().reconnect(),
|
||||
});
|
||||
@@ -141,7 +141,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.copyToClipboard',
|
||||
category: 'Data grid',
|
||||
name: 'Copy to clipboard',
|
||||
name: _t('command.datagrid.copy_to_clipboard.name', { defaultMessage: 'Copy to clipboard' }),
|
||||
keyText: 'CtrlOrCommand+C',
|
||||
disableHandleKeyText: 'CtrlOrCommand+C',
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
@@ -152,7 +152,7 @@
|
||||
id: 'dataGrid.editJsonDocument',
|
||||
category: 'Data grid',
|
||||
keyText: 'CtrlOrCommand+J',
|
||||
name: 'Edit row as JSON document',
|
||||
name: _t('command.datagrid.edit_json_document.name', { defaultMessage: 'Edit row as JSON document' }),
|
||||
testEnabled: () => getCurrentDataGrid()?.editJsonEnabled(),
|
||||
onClick: () => getCurrentDataGrid().editJsonDocument(),
|
||||
});
|
||||
@@ -160,15 +160,15 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.openSelectionInMap',
|
||||
category: 'Data grid',
|
||||
name: 'Open selection in map',
|
||||
testEnabled: () => getCurrentDataGrid() != null, // ?.openSelectionInMapEnabled(),
|
||||
name: _t('command.datagrid.open_selection_in_map.name', { defaultMessage: 'Open selection in map' }),
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
onClick: () => getCurrentDataGrid().openSelectionInMap(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.viewJsonDocument',
|
||||
category: 'Data grid',
|
||||
name: 'View row as JSON document',
|
||||
name: _t('command.datagrid.view_json_document.name', { defaultMessage: 'View row as JSON document' }),
|
||||
testEnabled: () => getCurrentDataGrid()?.viewJsonDocumentEnabled(),
|
||||
onClick: () => getCurrentDataGrid().viewJsonDocument(),
|
||||
});
|
||||
@@ -176,7 +176,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.viewJsonValue',
|
||||
category: 'Data grid',
|
||||
name: 'View cell as JSON document',
|
||||
name: _t('command.datagrid.view_json_value.name', { defaultMessage: 'View cell as JSON document' }),
|
||||
testEnabled: () => getCurrentDataGrid()?.viewJsonValueEnabled(),
|
||||
onClick: () => getCurrentDataGrid().viewJsonValue(),
|
||||
});
|
||||
@@ -184,7 +184,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.openJsonArrayInSheet',
|
||||
category: 'Data grid',
|
||||
name: 'Open array as table',
|
||||
name: _t('command.datagrid.open_json_array_in_sheet.name', { defaultMessage: 'Open array as table' }),
|
||||
testEnabled: () => getCurrentDataGrid()?.openJsonArrayInSheetEnabled(),
|
||||
onClick: () => getCurrentDataGrid().openJsonArrayInSheet(),
|
||||
});
|
||||
@@ -192,7 +192,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.saveCellToFile',
|
||||
category: 'Data grid',
|
||||
name: 'Save cell to file',
|
||||
name: _t('command.datagrid.save_cell_to_file.name', { defaultMessage: 'Save cell to file' }),
|
||||
testEnabled: () => getCurrentDataGrid()?.saveCellToFileEnabled(),
|
||||
onClick: () => getCurrentDataGrid().saveCellToFile(),
|
||||
});
|
||||
@@ -200,7 +200,7 @@
|
||||
registerCommand({
|
||||
id: 'dataGrid.loadCellFromFile',
|
||||
category: 'Data grid',
|
||||
name: 'Load cell from file',
|
||||
name: _t('command.datagrid.load_cell_from_file.name', { defaultMessage: 'Load cell from file' }),
|
||||
testEnabled: () => getCurrentDataGrid()?.loadCellFromFileEnabled(),
|
||||
onClick: () => getCurrentDataGrid().loadCellFromFile(),
|
||||
});
|
||||
@@ -212,7 +212,8 @@
|
||||
// testEnabled: () => getCurrentDataGrid()?.copyJsonEnabled(),
|
||||
// onClick: () => getCurrentDataGrid().copyJsonDocument(),
|
||||
// });
|
||||
|
||||
//
|
||||
//
|
||||
registerCommand({
|
||||
id: 'dataGrid.filterSelected',
|
||||
category: 'Data grid',
|
||||
@@ -221,7 +222,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.getDisplay().filterable,
|
||||
onClick: () => getCurrentDataGrid().filterSelectedValue(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.findColumn',
|
||||
category: 'Data grid',
|
||||
@@ -230,7 +230,6 @@
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
getSubCommands: () => getCurrentDataGrid().buildFindMenu(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.hideColumn',
|
||||
category: 'Data grid',
|
||||
@@ -239,7 +238,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.canShowLeftPanel(),
|
||||
onClick: () => getCurrentDataGrid().hideColumn(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.clearFilter',
|
||||
category: 'Data grid',
|
||||
@@ -248,7 +246,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.clearFilterEnabled(),
|
||||
onClick: () => getCurrentDataGrid().clearFilter(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.generateSqlFromData',
|
||||
category: 'Data grid',
|
||||
@@ -257,7 +254,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.generateSqlFromDataEnabled(),
|
||||
onClick: () => getCurrentDataGrid().generateSqlFromData(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.openFreeTable',
|
||||
category: 'Data grid',
|
||||
@@ -265,7 +261,6 @@
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
onClick: () => getCurrentDataGrid().openFreeTable(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.openChartFromSelection',
|
||||
category: 'Data grid',
|
||||
@@ -273,7 +268,6 @@
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
onClick: () => getCurrentDataGrid().openChartFromSelection(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.newJson',
|
||||
category: 'Data grid',
|
||||
@@ -281,7 +275,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.addJsonDocumentEnabled(),
|
||||
onClick: () => getCurrentDataGrid().addJsonDocument(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.editCellValue',
|
||||
category: 'Data grid',
|
||||
@@ -289,7 +282,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.editCellValueEnabled(),
|
||||
onClick: () => getCurrentDataGrid().editCellValue(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.mergeSelectedCellsIntoMirror',
|
||||
category: 'Data grid',
|
||||
@@ -297,7 +289,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
|
||||
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'merge', fullRows: false }),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.mergeSelectedRowsIntoMirror',
|
||||
category: 'Data grid',
|
||||
@@ -305,7 +296,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
|
||||
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'merge', fullRows: true }),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.appendSelectedCellsIntoMirror',
|
||||
category: 'Data grid',
|
||||
@@ -313,7 +303,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
|
||||
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'append', fullRows: false }),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.appendSelectedRowsIntoMirror',
|
||||
category: 'Data grid',
|
||||
@@ -321,7 +310,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
|
||||
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'append', fullRows: true }),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.replaceSelectedCellsIntoMirror',
|
||||
category: 'Data grid',
|
||||
@@ -329,7 +317,6 @@
|
||||
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
|
||||
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'replace', fullRows: false }),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.replaceSelectedRowsIntoMirror',
|
||||
category: 'Data grid',
|
||||
@@ -430,6 +417,7 @@
|
||||
import { openJsonLinesData } from '../utility/openJsonLinesData';
|
||||
import contextMenuActivator from '../utility/contextMenuActivator';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let onLoadNextData = undefined;
|
||||
export let grider = undefined;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let title;
|
||||
export let fieldDefinitions;
|
||||
@@ -42,7 +43,7 @@
|
||||
<FormSelectField
|
||||
isNative
|
||||
name="schemaName"
|
||||
label="Schema"
|
||||
label={_t('common.schema', { defaultMessage: 'Schema' })}
|
||||
options={schemaList.map(x => ({ label: x.schemaName, value: x.schemaName }))}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
registerCommand({
|
||||
id: 'dataForm.refresh',
|
||||
category: 'Data form',
|
||||
name: 'Refresh',
|
||||
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
keyText: 'F5 | CtrlOrCommand+R',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -197,6 +197,7 @@
|
||||
import resizeObserver from '../utility/resizeObserver';
|
||||
import openReferenceForm from './openReferenceForm';
|
||||
import { useSettings } from '../utility/metadataLoaders';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import FormTablesSelect from './FormTablesSelect.svelte';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import AceEditor from '../query/AceEditor.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let direction;
|
||||
export let storageTypeField;
|
||||
@@ -40,14 +41,22 @@
|
||||
$values[storageTypeField] == 'jsldata'
|
||||
? [{ value: 'jsldata', label: 'Query result data', directions: ['source'] }]
|
||||
: [
|
||||
{ value: 'database', label: 'Database', directions: ['source', 'target'] },
|
||||
{
|
||||
value: 'database',
|
||||
label: _t('common.database', { defaultMessage: 'Database' }),
|
||||
directions: ['source', 'target'],
|
||||
},
|
||||
...$extensions.fileFormats.map(format => ({
|
||||
value: format.storageType,
|
||||
label: `${format.name} files(s)`,
|
||||
directions: getFileFormatDirections(format),
|
||||
})),
|
||||
{ value: 'query', label: 'Query', directions: ['source'] },
|
||||
{ value: 'archive', label: 'Archive', directions: ['source', 'target'] },
|
||||
{ value: 'query', label: _t('common.query', { defaultMessage: 'Query' }), directions: ['source'] },
|
||||
{
|
||||
value: 'archive',
|
||||
label: _t('common.archive', { defaultMessage: 'Archive' }),
|
||||
directions: ['source', 'target'],
|
||||
},
|
||||
];
|
||||
|
||||
$: storageType = $values[storageTypeField];
|
||||
@@ -124,7 +133,7 @@
|
||||
conidName={connectionIdField}
|
||||
databaseName={databaseNameField}
|
||||
name={schemaNameField}
|
||||
label="Schema"
|
||||
label={_t('common.schema', { defaultMessage: 'Schema' })}
|
||||
/>
|
||||
{#if tablesField}
|
||||
<FormTablesSelect
|
||||
@@ -132,12 +141,12 @@
|
||||
schemaName={schemaNameField}
|
||||
databaseName={databaseNameField}
|
||||
name={tablesField}
|
||||
label="Tables / views / collections"
|
||||
label={_t('source.tables_views_collections', { defaultMessage: 'Tables / views / collections' })}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if storageType == 'query'}
|
||||
<div class="label">Query</div>
|
||||
<div class="label">{_t('common.query', { defaultMessage: 'Query' })}</div>
|
||||
<div class="sqlwrap">
|
||||
{#if $values.sourceQueryType == 'json'}
|
||||
<AceEditor value={$values.sourceQuery} on:input={e => setFieldValue('sourceQuery', e.detail)} mode="json" />
|
||||
@@ -156,7 +165,11 @@
|
||||
{/if}
|
||||
|
||||
{#if storageType == 'archive' && direction == 'source'}
|
||||
<FormArchiveFilesSelect label="Source files" folderName={$values[archiveFolderField]} name={tablesField} />
|
||||
<FormArchiveFilesSelect
|
||||
label={_t('source.source_files', { defaultMessage: 'Source files' })}
|
||||
folderName={$values[archiveFolderField]}
|
||||
name={tablesField}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if format && direction == 'source'}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let onSave;
|
||||
export let json;
|
||||
@@ -43,7 +44,7 @@
|
||||
|
||||
<div slot="footer">
|
||||
<FormStyledButton
|
||||
value="Save"
|
||||
value={_t('common.save', { defaultMessage: 'Save' })}
|
||||
data-testid="EditJsonModal_saveButton"
|
||||
on:click={() => {
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import { currentArchive } from '../stores';
|
||||
import { _t } from '../translations';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
|
||||
@@ -28,7 +29,7 @@
|
||||
<FormTextField label="File name" name="file" />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit value="Save" on:click={handleSubmit} />
|
||||
<FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import { _t } from '../translations';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import getElectron from '../utility/getElectron';
|
||||
@@ -56,7 +57,7 @@
|
||||
<svelte:fragment slot="header">Save file</svelte:fragment>
|
||||
<FormTextField label="File name" name="name" focused />
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit value="Save" on:click={handleSubmit} />
|
||||
<FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} />
|
||||
{#if electron}
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
import { derived } from 'svelte/store';
|
||||
import { safeFormatDate } from 'dbgate-tools';
|
||||
import FormDefaultActionField from './FormDefaultActionField.svelte';
|
||||
import { _t, getSelectedLanguage } from '../translations';
|
||||
import { internalRedirectTo } from '../clientAuth';
|
||||
|
||||
const electron = getElectron();
|
||||
let restartWarning = false;
|
||||
@@ -121,7 +123,27 @@ ORDER BY
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<FormCheckboxField name="tabGroup.showServerName" label="Show server name alongside database name in title of the tab group" defaultValue={false} />
|
||||
<FormCheckboxField
|
||||
name="tabGroup.showServerName"
|
||||
label="Show server name alongside database name in title of the tab group"
|
||||
defaultValue={false}
|
||||
/>
|
||||
<div class="heading">{_t('settings.localization', { defaultMessage: 'Localization' })}</div>
|
||||
<FormSelectField
|
||||
label="Language"
|
||||
name="localization.language"
|
||||
defaultValue={getSelectedLanguage()}
|
||||
isNative
|
||||
options={[
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'cs', label: 'Czech' },
|
||||
]}
|
||||
on:change={() => {
|
||||
setTimeout(() => {
|
||||
internalRedirectTo('/');
|
||||
}, 100);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="heading">Data grid</div>
|
||||
<FormTextField
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { closeCurrentModal } from '../modals/modalTools';
|
||||
import DataTypeEditor from './DataTypeEditor.svelte';
|
||||
import { editorAddColumn, editorDeleteColumn, editorModifyColumn, fillEditorColumnInfo } from 'dbgate-tools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let columnInfo;
|
||||
export let setTableInfo = null;
|
||||
@@ -75,7 +76,7 @@
|
||||
{#if !columnInfo}
|
||||
<FormButton
|
||||
type="button"
|
||||
value="Save"
|
||||
value={_t('common.save', { defaultMessage: 'Save' })}
|
||||
disabled={isReadOnly}
|
||||
on:click={e => {
|
||||
closeCurrentModal();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import _ from 'lodash';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let constraintInfo;
|
||||
export let setTableInfo;
|
||||
@@ -204,7 +205,7 @@
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit
|
||||
value="Save"
|
||||
value={_t('common.save', { defaultMessage: 'Save' })}
|
||||
disabled={isReadOnly}
|
||||
on:click={() => {
|
||||
closeCurrentModal();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" context="module">
|
||||
import { _t } from '../translations';
|
||||
|
||||
const getCurrentValueMarker: any = {};
|
||||
|
||||
export function shouldShowTab(tab, lockedDbModeArg = getCurrentValueMarker, currentDbArg = getCurrentValueMarker) {
|
||||
@@ -184,8 +186,8 @@
|
||||
};
|
||||
|
||||
function getTabDbName(tab, connectionList) {
|
||||
if (tab.tabComponent == 'ConnectionTab') return 'Connections';
|
||||
if (tab.tabComponent?.startsWith('Admin')) return 'Administration';
|
||||
if (tab.tabComponent == 'ConnectionTab') return _t('common.connections', { defaultMessage: 'Connections' });
|
||||
if (tab.tabComponent?.startsWith('Admin')) return _t('tab.administration', { defaultMessage: 'Administration' });
|
||||
if (tab.props && tab.props.conid && tab.props.database) return tab.props.database;
|
||||
if (tab.props && tab.props.conid) {
|
||||
const connection = connectionList?.find(x => x._id == tab.props.conid);
|
||||
@@ -232,7 +234,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.nextTab',
|
||||
category: 'Tabs',
|
||||
name: 'Next tab',
|
||||
name: _t('command.tabs.nextTab', { defaultMessage: 'Next tab' }),
|
||||
keyText: 'Ctrl+Tab',
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 2,
|
||||
onClick: () => switchTabByOrder(false),
|
||||
@@ -241,7 +243,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.previousTab',
|
||||
category: 'Tabs',
|
||||
name: 'Previous tab',
|
||||
name: _t('command.tabs.previousTab', { defaultMessage: 'Previous tab' }),
|
||||
keyText: 'Ctrl+Shift+Tab',
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 2,
|
||||
onClick: () => switchTabByOrder(true),
|
||||
@@ -250,7 +252,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.closeAll',
|
||||
category: 'Tabs',
|
||||
name: 'Close all tabs',
|
||||
name: _t('command.tabs.closeAll', { defaultMessage: 'Close all tabs' }),
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1,
|
||||
onClick: closeAll,
|
||||
});
|
||||
@@ -258,7 +260,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.closeTab',
|
||||
category: 'Tabs',
|
||||
name: 'Close tab',
|
||||
name: _t('command.tabs.closeTab', { defaultMessage: 'Close tab' }),
|
||||
keyText: isElectronAvailable() ? 'CtrlOrCommand+W' : 'CtrlOrCommand+Shift+W',
|
||||
testEnabled: () => {
|
||||
const hasAnyOtherTab = getOpenedTabs().filter(x => !x.closedTime).length >= 1;
|
||||
@@ -272,7 +274,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.closeTabsWithCurrentDb',
|
||||
category: 'Tabs',
|
||||
name: 'Close tabs with current DB',
|
||||
name: _t('command.tabs.closeTabsWithCurrentDb', { defaultMessage: 'Close tabs with current DB' }),
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1 && !!getCurrentDatabase(),
|
||||
onClick: closeTabsWithCurrentDb,
|
||||
});
|
||||
@@ -280,7 +282,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.closeTabsButCurrentDb',
|
||||
category: 'Tabs',
|
||||
name: 'Close tabs but current DB',
|
||||
name: _t('command.tabs.closeTabsButCurrentDb', { defaultMessage: 'Close tabs but current DB' }),
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1 && !!getCurrentDatabase(),
|
||||
onClick: closeTabsButCurrentDb,
|
||||
});
|
||||
@@ -288,7 +290,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.reopenClosedTab',
|
||||
category: 'Tabs',
|
||||
name: 'Reopen closed tab',
|
||||
name: _t('command.tabs.reopenClosedTab', { defaultMessage: 'Reopen closed tab' }),
|
||||
keyText: 'CtrlOrCommand+Shift+T',
|
||||
testEnabled: () => getOpenedTabs().filter(x => x.closedTime).length >= 1,
|
||||
onClick: reopenClosedTab,
|
||||
@@ -297,7 +299,7 @@
|
||||
registerCommand({
|
||||
id: 'tabs.addToFavorites',
|
||||
category: 'Tabs',
|
||||
name: 'Add current tab to favorites',
|
||||
name: _t('command.tabs.addToFavorites', { defaultMessage: 'Add current tab to favorites' }),
|
||||
// icon: 'icon favorite',
|
||||
// toolbar: true,
|
||||
testEnabled: () =>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import { useConfig } from '../utility/metadataLoaders';
|
||||
import ConnectionAdvancedDriverFields from '../settings/ConnectionAdvancedDriverFields.svelte';
|
||||
import DatabaseLoginModal from '../modals/DatabaseLoginModal.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let connection;
|
||||
export let tabid;
|
||||
@@ -289,7 +290,11 @@
|
||||
{:else}
|
||||
<FormButton value="Test" on:click={() => handleTest(false)} data-testid="ConnectionTab_buttonTest" />
|
||||
{/if}
|
||||
<FormButton value="Save" on:click={handleSave} data-testid="ConnectionTab_buttonSave" />
|
||||
<FormButton
|
||||
value={_t('common.save', { defaultMessage: 'Save' })}
|
||||
on:click={handleSave}
|
||||
data-testid="ConnectionTab_buttonSave"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="test-result">
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import { changeTab } from '../utility/common';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import DbKeyValueDetail from '../dbkeyvalue/DbKeyValueDetail.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
@@ -134,12 +135,16 @@
|
||||
</div>
|
||||
<FormStyledButton value={`TTL:${keyInfo.ttl}`} on:click={() => handleChangeTtl(keyInfo)} />
|
||||
{#if keyInfo.type == 'string'}
|
||||
<FormStyledButton value="Save" on:click={saveString} disabled={!editedValue} />
|
||||
<FormStyledButton
|
||||
value={_t('common.save', { defaultMessage: 'Save' })}
|
||||
on:click={saveString}
|
||||
disabled={!editedValue}
|
||||
/>
|
||||
{/if}
|
||||
{#if keyInfo.keyType?.addMethod && keyInfo.keyType?.showItemList}
|
||||
<FormStyledButton value="Add item" on:click={() => addItem(keyInfo)} />
|
||||
{/if}
|
||||
<FormStyledButton value="Refresh" on:click={refresh} />
|
||||
<FormStyledButton value={_t('common.refresh', { defaultMessage: 'Refresh' })} on:click={refresh} />
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
registerCommand({
|
||||
id: 'serverSummary.refresh',
|
||||
category: 'Server sumnmary',
|
||||
name: 'Refresh',
|
||||
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
keyText: 'F5 | CtrlOrCommand+R',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -22,6 +22,7 @@
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
|
||||
import ObjectListControl from '../elements/ObjectListControl.svelte';
|
||||
import { _t } from '../translations';
|
||||
import { apiCall } from '../utility/api';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import formatFileSize from '../utility/formatFileSize';
|
||||
|
||||
69
packages/web/src/translations.ts
Normal file
69
packages/web/src/translations.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import cs from '../../../translations/cs.json';
|
||||
|
||||
import MessageFormat, { MessageFunction } from '@messageformat/core';
|
||||
import { getStringSettingsValue } from './settings/settingsTools';
|
||||
|
||||
const translations = {
|
||||
en: {},
|
||||
cs,
|
||||
};
|
||||
const supportedLanguages = Object.keys(translations);
|
||||
|
||||
const compiledMessages: Partial<Record<string, Record<string, MessageFunction<'string'>>>> = {};
|
||||
|
||||
const defaultLanguage = 'en';
|
||||
|
||||
export function getSelectedLanguage(): string {
|
||||
const borwserLanguage = getBrowserLanguage();
|
||||
const selectedLanguage = getStringSettingsValue('localization.language', borwserLanguage);
|
||||
|
||||
if (!supportedLanguages.includes(selectedLanguage)) return defaultLanguage;
|
||||
|
||||
return selectedLanguage;
|
||||
}
|
||||
|
||||
export function getBrowserLanguage(): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
return (
|
||||
(navigator.languages && navigator.languages[0]).slice(0, 2) || navigator.language.slice(0, 2) || defaultLanguage
|
||||
);
|
||||
}
|
||||
return defaultLanguage;
|
||||
}
|
||||
|
||||
type TranslateOptions = {
|
||||
defaultMessage: string;
|
||||
values?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function getTranslation(key: string, defaultMessage: string, language: string) {
|
||||
const selectedTranslations = translations[language] ?? {};
|
||||
const translation = selectedTranslations[key];
|
||||
|
||||
if (!translation) {
|
||||
console.warn(`Translation not found for key: ${key}. For language: ${language}`);
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return translation;
|
||||
}
|
||||
|
||||
export function _t(key: string, options: TranslateOptions): string {
|
||||
const { defaultMessage, values } = options;
|
||||
|
||||
const selectedLanguage = getSelectedLanguage();
|
||||
|
||||
if (!compiledMessages[selectedLanguage]) {
|
||||
compiledMessages[selectedLanguage] = {};
|
||||
}
|
||||
|
||||
if (!compiledMessages[selectedLanguage][key]) {
|
||||
const translation = getTranslation(key, defaultMessage, selectedLanguage);
|
||||
const complied = new MessageFormat(selectedLanguage).compile(translation);
|
||||
compiledMessages[selectedLanguage][key] = complied;
|
||||
}
|
||||
|
||||
const compliledTranslation = compiledMessages[selectedLanguage][key];
|
||||
|
||||
return compliledTranslation(values ?? {});
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
import SingleConnectionDatabaseList from './SingleConnectionDatabaseList.svelte';
|
||||
import _ from 'lodash';
|
||||
import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let hidden = false;
|
||||
let domSqlObjectList = null;
|
||||
@@ -29,16 +30,26 @@
|
||||
|
||||
<WidgetColumnBar {hidden}>
|
||||
{#if $config?.singleConnection}
|
||||
<WidgetColumnBarItem title="Databases" name="databases" height="35%" storageName="databasesWidget">
|
||||
<WidgetColumnBarItem
|
||||
title={_t('widget.databases', { defaultMessage: 'Databases' })}
|
||||
name="databases"
|
||||
height="35%"
|
||||
storageName="databasesWidget"
|
||||
>
|
||||
<SingleConnectionDatabaseList connection={$config?.singleConnection} />
|
||||
</WidgetColumnBarItem>
|
||||
{:else if !$config?.singleDbConnection}
|
||||
<WidgetColumnBarItem title="Connections" name="connections" height="35%" storageName="connectionsWidget">
|
||||
<WidgetColumnBarItem
|
||||
title={_t('common.connections', { defaultMessage: 'Connections' })}
|
||||
name="connections"
|
||||
height="35%"
|
||||
storageName="connectionsWidget"
|
||||
>
|
||||
<ConnectionList passProps={{ onFocusSqlObjectList: () => domSqlObjectList.focus() }} />
|
||||
</WidgetColumnBarItem>
|
||||
{/if}
|
||||
<WidgetColumnBarItem
|
||||
title="Pinned"
|
||||
title={_t('widget.pinned', { defaultMessage: 'Pinned' })}
|
||||
name="pinned"
|
||||
height="15%"
|
||||
storageName="pinnedItemsWidget"
|
||||
@@ -51,7 +62,7 @@
|
||||
<WidgetColumnBarItem
|
||||
title={driver?.databaseEngineTypes?.includes('document')
|
||||
? (driver?.collectionPluralLabel ?? 'Collections/containers')
|
||||
: 'Tables, views, functions'}
|
||||
: _t('widget.tables_views_functions', { defaultMessage: 'Tables, views, functions' })}
|
||||
name="dbObjects"
|
||||
storageName="dbObjectsWidget"
|
||||
skip={!(
|
||||
@@ -64,7 +75,7 @@
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
<WidgetColumnBarItem
|
||||
title={'Keys'}
|
||||
title={_t('widget.keys', { defaultMessage: 'Keys' })}
|
||||
name="dbObjects"
|
||||
storageName="dbObjectsWidget"
|
||||
skip={!(conid && (database || singleDatabase) && driver?.databaseEngineTypes?.includes('keyvalue'))}
|
||||
@@ -73,7 +84,7 @@
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
<WidgetColumnBarItem
|
||||
title="Database content"
|
||||
title={_t('widget.database_content', { defaultMessage: 'Database content' })}
|
||||
name="dbObjects"
|
||||
storageName="dbObjectsWidget"
|
||||
skip={conid && (database || singleDatabase)}
|
||||
@@ -86,7 +97,7 @@
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
<WidgetColumnBarItem
|
||||
title="Database content"
|
||||
title={_t('widget.database_content', { defaultMessage: 'Database content' })}
|
||||
name="dbObjects"
|
||||
storageName="dbObjectsWidget"
|
||||
skip={!(conid && (database || singleDatabase) && !driver)}
|
||||
@@ -94,7 +105,9 @@
|
||||
<WidgetsInnerContainer>
|
||||
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
|
||||
|
||||
<ErrorInfo message="Invalid database connection, driver not found" />
|
||||
<ErrorInfo
|
||||
message={_t('error.driver_not_found', { defaultMessage: 'Invalid database connection, driver not found' })}
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { useFavorites } from '../utility/metadataLoaders';
|
||||
import { _t } from '../translations';
|
||||
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { appliedCurrentSchema, currentDatabase } from '../stores';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { extractDbNameFromComposite, extractSchemaNameFromComposite, findDefaultSchema } from 'dbgate-tools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let schemaList;
|
||||
export let objectList;
|
||||
@@ -58,9 +59,9 @@
|
||||
|
||||
function handleCreateSchema() {
|
||||
showModal(InputTextModal, {
|
||||
header: 'Create schema',
|
||||
header: _t('schema.create_schema', { defaultMessage: 'Create schema' }),
|
||||
value: 'newschema',
|
||||
label: 'Schema name',
|
||||
label: _t('schema.schema_name', { defaultMessage: 'Schema name' }),
|
||||
onConfirm: async name => {
|
||||
const dbid = { conid, database };
|
||||
await runOperationOnDatabase(
|
||||
@@ -105,13 +106,19 @@
|
||||
|
||||
{#if realSchemaList.length > 0}
|
||||
<div class="wrapper" class:negativeMarginTop>
|
||||
<div class="mr-1">Schema:</div>
|
||||
<div class="mr-1">{_t('common.schema', { defaultMessage: 'Schema' })}:</div>
|
||||
<SelectField
|
||||
isNative
|
||||
options={connection?.useSeparateSchemas
|
||||
? (schemaList?.map(x => ({ label: x.schemaName, value: x.schemaName })) ?? [])
|
||||
: [
|
||||
{ label: `All schemas (${objectList?.length ?? 0})`, value: '' },
|
||||
{
|
||||
label: _t('schema.all_schemas', {
|
||||
defaultMessage: 'All schemas ({count})',
|
||||
values: { count: objectList?.length ?? 0 },
|
||||
}),
|
||||
value: '',
|
||||
},
|
||||
...realSchemaList.map(x => ({ label: `${x} (${countBySchema[x] ?? 0})`, value: x })),
|
||||
]}
|
||||
value={selectedSchema ?? $appliedCurrentSchema ?? ''}
|
||||
@@ -135,15 +142,20 @@
|
||||
selectedSchema = null;
|
||||
localStorage.removeItem(valueStorageKey);
|
||||
}}
|
||||
title="Reset to default"
|
||||
title={_t('schema.reset_to_default', { defaultMessage: 'Reset to default' })}
|
||||
>
|
||||
<FontIcon icon="icon close" />
|
||||
</InlineButton>
|
||||
{/if}
|
||||
<InlineButton on:click={handleCreateSchema} title="Add new schema" square>
|
||||
<InlineButton on:click={handleCreateSchema} title={_t('schema.add', { defaultMessage: 'Add new schema' })} square>
|
||||
<FontIcon icon="icon plus-thick" />
|
||||
</InlineButton>
|
||||
<InlineButton on:click={handleDropSchema} title="Delete schema" square disabled={!$appliedCurrentSchema}>
|
||||
<InlineButton
|
||||
on:click={handleDropSchema}
|
||||
title={_t('schema.delete', { defaultMessage: 'Delete schema' })}
|
||||
square
|
||||
disabled={!$appliedCurrentSchema}
|
||||
>
|
||||
<FontIcon icon="icon minus-thick" />
|
||||
</InlineButton>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"exclude": ["node_modules/*", "public/*"],
|
||||
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -12,7 +13,7 @@
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": false,
|
||||
"strict": false,
|
||||
"target": "es6",
|
||||
"target": "es6"
|
||||
// "allowJs": true,
|
||||
// "checkJs": true,
|
||||
}
|
||||
|
||||
1
translations/cs.json
Normal file
1
translations/cs.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
58
translations/en.json
Normal file
58
translations/en.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"app.preparing_pluguns": "Preparing plugins ...",
|
||||
"app.starting": "Starting DbGate",
|
||||
"command.data_grid.revert_all_changes.name": "Revert all changes",
|
||||
"command.data_grid.revert_all_changes.toolbar_name": "Revert all",
|
||||
"command.data_grid.revert_row_changes": "Revert row changes",
|
||||
"command.datagrid.add_new_column.name": "Add new column",
|
||||
"command.datagrid.add_new_column.toolbar_name": "New column",
|
||||
"command.datagrid.clone_rows.name": "Clone rows",
|
||||
"command.datagrid.clone_rows.toolbar_name": "Clone row(s)",
|
||||
"command.datagrid.copy_to_clipboard.name": "Copy to clipboard",
|
||||
"command.datagrid.delete_selected_rows.name": "Delete selected rows",
|
||||
"command.datagrid.delete_selected_rows.toolbar_name": "Delete row(s)",
|
||||
"command.datagrid.edit_json_document.name": "Edit row as JSON document",
|
||||
"command.datagrid.insert_new_row.name": "Insert new row",
|
||||
"command.datagrid.insert_new_row.toolbar_name": "New row",
|
||||
"command.datagrid.load_cell_from_file.name": "Load cell from file",
|
||||
"command.datagrid.open_json_array_in_sheet.name": "Open array as table",
|
||||
"command.datagrid.open_selection_in_map.name": "Open selection in map",
|
||||
"command.datagrid.reconnect.name": "Reconnect",
|
||||
"command.datagrid.redo.name": "Redo",
|
||||
"command.datagrid.remove_field.name": "Remove field",
|
||||
"command.datagrid.save_cell_to_file.name": "Save cell to file",
|
||||
"command.datagrid.set_null.name": "Set NULL",
|
||||
"command.datagrid.undo.name": "Undo",
|
||||
"command.datagrid.view_json_document.name": "View row as JSON document",
|
||||
"command.datagrid.view_json_value.name": "View cell as JSON document",
|
||||
"command.tabs.addToFavorites": "Add current tab to favorites",
|
||||
"command.tabs.closeAll": "Close all tabs",
|
||||
"command.tabs.closeTab": "Close tab",
|
||||
"command.tabs.closeTabsButCurrentDb": "Close tabs but current DB",
|
||||
"command.tabs.closeTabsWithCurrentDb": "Close tabs with current DB",
|
||||
"command.tabs.nextTab": "Next tab",
|
||||
"command.tabs.previousTab": "Previous tab",
|
||||
"command.tabs.reopenClosedTab": "Reopen closed tab",
|
||||
"common.archive": "Archive",
|
||||
"common.connections": "Connections",
|
||||
"common.database": "Database",
|
||||
"common.query": "Query",
|
||||
"common.refresh": "Refresh",
|
||||
"common.save": "Save",
|
||||
"common.schema": "Schema",
|
||||
"error.driver_not_found": "Invalid database connection, driver not found",
|
||||
"schema.add": "Add new schema",
|
||||
"schema.create_schema": "Create schema",
|
||||
"schema.delete": "Delete schema",
|
||||
"schema.reset_to_default": "Reset to default",
|
||||
"schema.schema_name": "Schema name",
|
||||
"settings.localization": "Localization",
|
||||
"source.source_files": "Source files",
|
||||
"source.tables_views_collections": "Tables / views / collections",
|
||||
"tab.administration": "Administration",
|
||||
"widget.database_content": "Database content",
|
||||
"widget.databases": "Databases",
|
||||
"widget.keys": "Keys",
|
||||
"widget.pinned": "Pinned",
|
||||
"widget.tables_views_functions": "Tables, views, functions"
|
||||
}
|
||||
@@ -7,7 +7,7 @@ checkout-and-merge-pro:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
|
||||
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
79
yarn.lock
79
yarn.lock
@@ -1708,6 +1708,42 @@
|
||||
resolved "https://registry.yarnpkg.com/@mdi/font/-/font-7.4.47.tgz#2ae522867da3a5c88b738d54b403eb91471903af"
|
||||
integrity sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==
|
||||
|
||||
"@messageformat/core@^3.4.0":
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@messageformat/core/-/core-3.4.0.tgz#2814c23383dec7bddf535d54f2a03e410165ca9f"
|
||||
integrity sha512-NgCFubFFIdMWJGN5WuQhHCNmzk7QgiVfrViFxcS99j7F5dDS5EP6raR54I+2ydhe4+5/XTn/YIEppFaqqVWHsw==
|
||||
dependencies:
|
||||
"@messageformat/date-skeleton" "^1.0.0"
|
||||
"@messageformat/number-skeleton" "^1.0.0"
|
||||
"@messageformat/parser" "^5.1.0"
|
||||
"@messageformat/runtime" "^3.0.1"
|
||||
make-plural "^7.0.0"
|
||||
safe-identifier "^0.4.1"
|
||||
|
||||
"@messageformat/date-skeleton@^1.0.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz#3bad068cbf5873d14592cfc7a73dd4d8615e2739"
|
||||
integrity sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==
|
||||
|
||||
"@messageformat/number-skeleton@^1.0.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz#e7c245c41a1b2722bc59dad68f4d454f761bc9b4"
|
||||
integrity sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==
|
||||
|
||||
"@messageformat/parser@^5.1.0":
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@messageformat/parser/-/parser-5.1.1.tgz#ca7d6c18e9f3f6b6bc984a465dac16da00106055"
|
||||
integrity sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==
|
||||
dependencies:
|
||||
moo "^0.5.1"
|
||||
|
||||
"@messageformat/runtime@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@messageformat/runtime/-/runtime-3.0.1.tgz#94d1f6c43265c28ef7aed98ecfcc0968c6c849ac"
|
||||
integrity sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==
|
||||
dependencies:
|
||||
make-plural "^7.0.0"
|
||||
|
||||
"@mongodb-js/saslprep@^1.1.5":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.6.tgz#54da3f794c71a17445740fe2b74882e0c76a3058"
|
||||
@@ -1827,6 +1863,13 @@
|
||||
magic-string "^0.25.7"
|
||||
resolve "^1.17.0"
|
||||
|
||||
"@rollup/plugin-json@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-6.1.0.tgz#fbe784e29682e9bb6dee28ea75a1a83702e7b805"
|
||||
integrity sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^5.1.0"
|
||||
|
||||
"@rollup/plugin-node-resolve@^13.0.5":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c"
|
||||
@@ -1872,6 +1915,15 @@
|
||||
estree-walker "^1.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@rollup/pluginutils@^5.1.0":
|
||||
version "5.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a"
|
||||
integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.0"
|
||||
estree-walker "^2.0.2"
|
||||
picomatch "^4.0.2"
|
||||
|
||||
"@sinclair/typebox@^0.24.1":
|
||||
version "0.24.51"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
|
||||
@@ -2365,6 +2417,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||
|
||||
"@types/estree@^1.0.0":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
||||
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
||||
|
||||
"@types/fs-extra@^8.0.1":
|
||||
version "8.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.5.tgz#33aae2962d3b3ec9219b5aca2555ee00274f5927"
|
||||
@@ -4979,7 +5036,7 @@ estree-walker@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
|
||||
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
|
||||
|
||||
estree-walker@^2.0.1:
|
||||
estree-walker@^2.0.1, estree-walker@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
@@ -8469,6 +8526,11 @@ make-fetch-happen@^9.0.1:
|
||||
socks-proxy-agent "^6.0.0"
|
||||
ssri "^8.0.0"
|
||||
|
||||
make-plural@^7.0.0:
|
||||
version "7.4.0"
|
||||
resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.4.0.tgz#fa6990dd550dea4de6b20163f74e5ed83d8a8d6d"
|
||||
integrity sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==
|
||||
|
||||
makeerror@1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
|
||||
@@ -8798,6 +8860,11 @@ mongodb@^6.3.0:
|
||||
bson "^6.7.0"
|
||||
mongodb-connection-string-url "^3.0.0"
|
||||
|
||||
moo@^0.5.1:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"
|
||||
integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==
|
||||
|
||||
mri@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
||||
@@ -9602,6 +9669,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
picomatch@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
|
||||
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
|
||||
|
||||
pify@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
@@ -10442,6 +10514,11 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, s
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-identifier@^0.4.1:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb"
|
||||
integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==
|
||||
|
||||
safe-regex-test@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
|
||||
|
||||
Reference in New Issue
Block a user