refactor: update import statements to use node built-in modules

chore: update lerna configuration formatting

chore: bump version in package.json to 6.0.0

fix: modify build scripts for improved readability and consistency

feat: enhance translation scripts with better error handling and progress tracking

chore: improve plugin creation script for better user experience

style: format svgo configuration for consistency
This commit is contained in:
Harvey Zack committed 2025-08-03 18:34:30 +08:00
1 parent 3540cd0cce
commit fa755b5f61
11 files changed
+509 -496

No files matched your search

+3 -3
View File
@@ -1,5 +1,5 @@
{
"packages": ["packages/*"],
"version": "independent",
"npmClient": "yarn"
"packages": ["packages/*"],
"version": "independent",
"npmClient": "yarn"
}
+5 -6
View File
@@ -1,7 +1,7 @@
{
"name": "artplayer-packages",
"type": "module",
"version": "5.2.0",
"version": "6.0.0",
"private": true,
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
"workspaces": [
@@ -25,17 +25,17 @@
"node": ">= 20.0.0"
},
"scripts": {
"build:all": "npm run build all && npm run build:i18n && npm run build:ts && npm run build:docs",
"lint": "npx eslint packages/*/{src,types,package.json} --fix",
"bootstrap": "npx lerna link",
"postinstall": "npx lerna run prepare",
"build:ts": "node ./scripts/build-ts.js",
"build:docs": "node ./scripts/build-docs.js",
"build:i18n": "node ./scripts/build-i18n.js",
"trans:docs": "node ./scripts/trans-docs.js",
"create:plugin": "node ./scripts/plugin/create.js",
"lint": "npx eslint packages/*/{src,types,package.json} --fix",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
"postinstall": "npx lerna run prepare",
"bootstrap": "npx lerna link"
"build:all": "npm run build all && npm run build:i18n && npm run build:ts && npm run build:docs"
},
"browserslist": "last 1 Chrome version",
"dependencies": {
@@ -52,7 +52,6 @@
"dotenv": "^17.2.1",
"eslint": "^9.32.0",
"glob": "^11.0.3",
"globals": "^16.3.0",
"lerna": "^8.2.3",
"prompts": "^2.4.2",
"servor": "^4.0.2",
+6 -5
View File
@@ -1,8 +1,9 @@
import spawn from 'cross-spawn';
import process from 'node:process'
import spawn from 'cross-spawn'
const proc = spawn('npm', ['run', 'build'], {
cwd: './packages/artplayer-vitepress/',
});
cwd: './packages/artplayer-vitepress/',
})
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
proc.stdout.pipe(process.stdout)
proc.stderr.pipe(process.stderr)
+37 -36
View File
@@ -1,45 +1,46 @@
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
import { Parcel } from '@parcel/core';
import fs from 'node:fs'
import path from 'node:path'
import { Parcel } from '@parcel/core'
import { glob } from 'glob'
const basePath = 'packages/artplayer';
const basePath = 'packages/artplayer'
const entries = glob.sync(path.join(basePath, 'src/i18n/*.js'), {
ignore: [path.join(basePath, 'src/i18n/index.js'), path.join(basePath, 'src/i18n/zh-cn.js')],
});
ignore: [path.join(basePath, 'src/i18n/index.js'), path.join(basePath, 'src/i18n/zh-cn.js')],
})
const packagePath = path.join(basePath, 'package.json');
const packagePath = path.join(basePath, 'package.json')
const backupPackagePath = path.join(basePath, 'package_backup.json');
(async function build() {
fs.renameSync(packagePath, backupPackagePath);
fs.renameSync(packagePath, backupPackagePath)
const bundler = new Parcel({
entries: entries,
defaultConfig: '@parcel/config-default',
mode: 'production',
defaultTargetOptions: {
distDir: 'packages/artplayer/dist/i18n',
outputFormat: 'global',
sourceMaps: false,
isLibrary: true,
engines: {
browsers: ['last 1 Chrome version'],
},
},
env: {
NODE_ENV: 'production',
},
});
const bundler = new Parcel({
entries,
defaultConfig: '@parcel/config-default',
mode: 'production',
defaultTargetOptions: {
distDir: 'packages/artplayer/dist/i18n',
outputFormat: 'global',
sourceMaps: false,
isLibrary: true,
engines: {
browsers: ['last 1 Chrome version'],
},
},
env: {
NODE_ENV: 'production',
},
})
try {
const { bundleGraph, buildTime } = await bundler.run();
const bundles = bundleGraph.getBundles();
console.log(`✨ Built [i18n] ${bundles.length} bundles in ${buildTime}ms`);
fs.renameSync(backupPackagePath, packagePath);
} catch (err) {
console.log(err.diagnostics);
fs.renameSync(backupPackagePath, packagePath);
}
})();
try {
const { bundleGraph, buildTime } = await bundler.run()
const bundles = bundleGraph.getBundles()
console.log(`✨ Built [i18n] ${bundles.length} bundles in ${buildTime}ms`)
fs.renameSync(backupPackagePath, packagePath)
}
catch (err) {
console.log(err.diagnostics)
fs.renameSync(backupPackagePath, packagePath)
}
})()
+126 -124
View File
@@ -1,161 +1,163 @@
import fs from 'fs';
import cpy from 'cpy';
import path from 'path';
import prompts from 'prompts';
import { Parcel } from '@parcel/core';
import { getProjects } from './utils.js';
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { Parcel } from '@parcel/core'
import cpy from 'cpy'
import prompts from 'prompts'
import { getProjects } from './utils.js'
const projects = getProjects();
const compiledPath = path.resolve('docs/compiled');
const projects = getProjects()
const compiledPath = path.resolve('docs/compiled')
function compressString(input) {
return input
.replace(/\s*(<[^>]+>)\s*/g, '$1')
.replace(/(\r\n|\n|\r)/gm, ' ')
.replace(/\s+/g, ' ')
.trim();
return input
.replace(/\s*(<[^>]+>)\s*/g, '$1')
.replace(/(\r\n|\n|\r)/g, ' ')
.replace(/\s+/g, ' ')
.trim()
}
async function build(name, targetName) {
const projectPackageJson = path.join(projects[name], 'package.json');
const { version } = JSON.parse(fs.readFileSync(projectPackageJson, 'utf-8'));
const projectPackageJson = path.join(projects[name], 'package.json')
const { version } = JSON.parse(fs.readFileSync(projectPackageJson, 'utf-8'))
process.chdir(projects[name]);
process.chdir(projects[name])
const targets = {
main: {
context: 'browser',
distDir: path.join(projects[name], 'dist'),
sourceMap: false,
outputFormat: 'global',
isLibrary: true,
engines: {
browsers: 'last 1 Chrome version',
},
},
legacy: {
context: 'browser',
distDir: path.join(projects[name], 'dist'),
sourceMap: false,
outputFormat: 'global',
isLibrary: true,
engines: {
browsers: 'IE 11',
},
},
esm: {
context: 'browser',
distDir: path.join(projects[name], 'dist'),
sourceMap: true,
outputFormat: 'esmodule',
isLibrary: true,
engines: {
browsers: 'last 1 Chrome version',
},
},
};
const targets = {
main: {
context: 'browser',
distDir: path.join(projects[name], 'dist'),
sourceMap: false,
outputFormat: 'global',
isLibrary: true,
engines: {
browsers: 'last 1 Chrome version',
},
},
legacy: {
context: 'browser',
distDir: path.join(projects[name], 'dist'),
sourceMap: false,
outputFormat: 'global',
isLibrary: true,
engines: {
browsers: 'IE 11',
},
},
esm: {
context: 'browser',
distDir: path.join(projects[name], 'dist'),
sourceMap: true,
outputFormat: 'esmodule',
isLibrary: true,
engines: {
browsers: 'last 1 Chrome version',
},
},
}
const names = {
main: name,
legacy: `${name}.legacy`,
esm: `${name}.esm`,
};
const names = {
main: name,
legacy: `${name}.legacy`,
esm: `${name}.esm`,
}
const entryFile = path.join(projects[name], 'src/index.js');
const entryFile = path.join(projects[name], 'src/index.js')
const bundler = new Parcel({
entries: entryFile,
defaultConfig: '@parcel/config-default',
mode: 'production',
targets: {
[targetName]: targets[targetName],
},
env: {
NODE_ENV: 'production',
},
});
const bundler = new Parcel({
entries: entryFile,
defaultConfig: '@parcel/config-default',
mode: 'production',
targets: {
[targetName]: targets[targetName],
},
env: {
NODE_ENV: 'production',
},
})
const banner = `
const banner = `
/*!
* ${name}.js v${version}
* Github: https://github.com/zhw2590582/ArtPlayer
* (c) 2017-${new Date().getFullYear()} Harvey Zack
* Released under the MIT License.
*/
`;
`
const { bundleGraph, buildTime } = await bundler.run();
const bundles = bundleGraph.getBundles();
const { bundleGraph, buildTime } = await bundler.run()
const bundles = bundleGraph.getBundles()
const filePath = path.join(projects[name], 'dist/index.js');
const newFilePath = path.join(projects[name], `dist/${names[targetName]}.js`);
const code = fs.readFileSync(filePath, 'utf-8');
const filePath = path.join(projects[name], 'dist/index.js')
const newFilePath = path.join(projects[name], `dist/${names[targetName]}.js`)
const code = fs.readFileSync(filePath, 'utf-8')
fs.writeFileSync(filePath, banner + compressString(code));
fs.renameSync(filePath, newFilePath);
const size = fs.statSync(newFilePath).size / 1024;
await cpy(newFilePath, compiledPath);
fs.writeFileSync(filePath, banner + compressString(code))
fs.renameSync(filePath, newFilePath)
const size = fs.statSync(newFilePath).size / 1024
await cpy(newFilePath, compiledPath)
if (targetName === 'esm') {
const jsFile = path.join(projects[name], `dist/${names.esm}.js`);
const oldMapName = 'index.js.map';
const newMapName = `${names.esm}.js.map`;
if (targetName === 'esm') {
const jsFile = path.join(projects[name], `dist/${names.esm}.js`)
const oldMapName = 'index.js.map'
const newMapName = `${names.esm}.js.map`
const oldMapPath = path.join(projects[name], 'dist', oldMapName);
const newMapPath = path.join(projects[name], 'dist', newMapName);
if (fs.existsSync(oldMapPath)) {
fs.renameSync(oldMapPath, newMapPath);
}
let jsContent = fs.readFileSync(jsFile, 'utf8');
jsContent = jsContent.replace(/\/\/# sourceMappingURL=.*\.js\.map/, `\n//# sourceMappingURL=${newMapName}`);
fs.writeFileSync(jsFile, jsContent);
await cpy(newMapPath, compiledPath);
await cpy(newFilePath, compiledPath);
const oldMapPath = path.join(projects[name], 'dist', oldMapName)
const newMapPath = path.join(projects[name], 'dist', newMapName)
if (fs.existsSync(oldMapPath)) {
fs.renameSync(oldMapPath, newMapPath)
}
console.log(
`✨ Built@${targetName} ${name}@${version}`,
`Bundles@${bundles.length}`,
`Time@${buildTime}ms`,
`Size@${size.toFixed(2)}kb`,
);
let jsContent = fs.readFileSync(jsFile, 'utf8')
jsContent = jsContent.replace(/\/\/# sourceMappingURL=.*\.js\.map/, `\n//# sourceMappingURL=${newMapName}`)
fs.writeFileSync(jsFile, jsContent)
await cpy(newMapPath, compiledPath)
await cpy(newFilePath, compiledPath)
}
console.log(
`✨ Built@${targetName} ${name}@${version}`,
`Bundles@${bundles.length}`,
`Time@${buildTime}ms`,
`Size@${size.toFixed(2)}kb`,
)
}
async function runBuild() {
if (process.argv.pop() === 'all') {
const bundles = Object.keys(projects).map((name) => async () => {
await build(name, 'main');
await build(name, 'legacy');
await build(name, 'esm');
});
if (process.argv.pop() === 'all') {
const bundles = Object.keys(projects).map(name => async () => {
await build(name, 'main')
await build(name, 'legacy')
await build(name, 'esm')
})
await runPromisesInSeries(bundles);
console.log('✅ Finished building all packages!');
} else {
const response = await prompts({
type: 'select',
name: 'value',
message: 'Which project do you want to build?',
choices: Object.keys(projects).map((name) => ({
title: name,
value: name,
})),
initial: 0,
});
await runPromisesInSeries(bundles)
console.log('✅ Finished building all packages!')
}
else {
const response = await prompts({
type: 'select',
name: 'value',
message: 'Which project do you want to build?',
choices: Object.keys(projects).map(name => ({
title: name,
value: name,
})),
initial: 0,
})
if (response.value) {
await build(response.value, 'main');
await build(response.value, 'legacy');
await build(response.value, 'esm');
}
if (response.value) {
await build(response.value, 'main')
await build(response.value, 'legacy')
await build(response.value, 'esm')
}
}
}
function runPromisesInSeries(ps) {
return ps.reduce((p, next) => p.then(next), Promise.resolve());
return ps.reduce((p, next) => p.then(next), Promise.resolve())
}
runBuild().catch((error) => {
console.error('❌ Build script encountered an error:', error);
});
console.error('❌ Build script encountered an error:', error)
})
+78 -76
View File
@@ -1,92 +1,94 @@
import path from 'path';
import servor from 'servor';
import prompts from 'prompts';
import { Parcel } from '@parcel/core';
import { fileURLToPath } from 'url';
import { getProjects } from './utils.js';
import openBrowser from 'servor/utils/openBrowser.js';
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { Parcel } from '@parcel/core'
import prompts from 'prompts'
import servor from 'servor'
import openBrowser from 'servor/utils/openBrowser.js'
import { getProjects } from './utils.js'
const projects = getProjects();
const projects = getProjects()
async function develop(name) {
const projectPath = projects[name];
const uncompiledPath = path.resolve(`docs/uncompiled/${name}`);
const entryFile = path.join(projectPath, 'src/index.js');
const projectPath = projects[name]
const uncompiledPath = path.resolve(`docs/uncompiled/${name}`)
const entryFile = path.join(projectPath, 'src/index.js')
let isOpenBrowser = false;
let isOpenBrowser = false
const { url } = await servor({
root: 'docs',
fallback: 'index.html',
reload: true,
port: 8082,
});
const { url } = await servor({
root: 'docs',
fallback: 'index.html',
reload: true,
port: 8082,
})
process.chdir(projectPath);
process.chdir(projectPath)
const bundler = new Parcel({
entries: entryFile,
defaultConfig: '@parcel/config-default',
mode: 'development',
targets: {
main: {
distDir: uncompiledPath,
outputFormat: 'global',
isLibrary: true,
engines: {
browsers: ['last 1 Chrome version'],
},
},
const bundler = new Parcel({
entries: entryFile,
defaultConfig: '@parcel/config-default',
mode: 'development',
targets: {
main: {
distDir: uncompiledPath,
outputFormat: 'global',
isLibrary: true,
engines: {
browsers: ['last 1 Chrome version'],
},
env: {
NODE_ENV: 'development',
},
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
});
},
},
env: {
NODE_ENV: 'development',
},
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
})
bundler.watch((error, event) => {
if (error) {
console.error(`[${name}] ❌ Build error:`, error);
return;
}
bundler.watch((error, event) => {
if (error) {
console.error(`[${name}] ❌ Build error:`, error)
return
}
if (event.type === 'buildSuccess') {
const bundles = event.bundleGraph.getBundles();
console.log(`[${name}] ✅ Built ${bundles.length} bundles in ${event.buildTime}ms`);
if (!isOpenBrowser) {
isOpenBrowser = true;
openBrowser(url);
}
}
if (event.type === 'buildSuccess') {
const bundles = event.bundleGraph.getBundles()
console.log(`[${name}] ✅ Built ${bundles.length} bundles in ${event.buildTime}ms`)
if (!isOpenBrowser) {
isOpenBrowser = true
openBrowser(url)
}
}
if (event.type === 'buildFailure') {
console.error(`[${name}] ❌ Build failure:`, event.diagnostics);
}
});
if (event.type === 'buildFailure') {
console.error(`[${name}] ❌ Build failure:`, event.diagnostics)
}
})
}
(async () => {
try {
const response = await prompts({
type: 'select',
name: 'value',
message: 'Which project do you want to develop?',
choices: Object.keys(projects).map((name) => ({
title: name,
value: name,
})),
initial: 0,
});
try {
const response = await prompts({
type: 'select',
name: 'value',
message: 'Which project do you want to develop?',
choices: Object.keys(projects).map(name => ({
title: name,
value: name,
})),
initial: 0,
})
if (response.value) {
await develop(response.value);
}
} catch (error) {
console.error(`❌ Prompt error: ${error.message}`);
if (response.value) {
await develop(response.value)
}
})();
}
catch (error) {
console.error(`❌ Prompt error: ${error.message}`)
}
})()
+54 -52
View File
@@ -1,81 +1,83 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
// Get the current file path
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// Get the plugin name from command line arguments
const args = process.argv.slice(2);
const pluginName = args[0];
const args = process.argv.slice(2)
const pluginName = args[0]
if (!pluginName) {
console.error('Please provide a plugin name, e.g., npm run create:plugin somePluginName');
process.exit(1);
console.error('Please provide a plugin name, e.g., npm run create:plugin somePluginName')
process.exit(1)
}
// Validate plugin name
const pluginNameRegex = /^[a-z-]+$/;
const pluginNameRegex = /^[a-z-]+$/
if (!pluginNameRegex.test(pluginName)) {
console.error('Invalid plugin name. Only lowercase letters and hyphens are allowed.');
process.exit(1);
console.error('Invalid plugin name. Only lowercase letters and hyphens are allowed.')
process.exit(1)
}
// Convert plugin name to export name (PascalCase)
const exportName = pluginName
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join('');
const exportNameFull = `artplayerPlugin${exportName}`;
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join('')
const exportNameFull = `artplayerPlugin${exportName}`
// Define paths
const templateDir = path.join(__dirname, 'template');
const destDir = path.join(__dirname, '../../packages', `artplayer-plugin-${pluginName}`);
const exampleDir = path.join(__dirname, '../../docs/assets/example');
const exampleFileName = `${pluginName.replace(/-/g, '.')}.js`;
const exampleFile = path.join(exampleDir, exampleFileName);
const templateDir = path.join(__dirname, 'template')
const destDir = path.join(__dirname, '../../packages', `artplayer-plugin-${pluginName}`)
const exampleDir = path.join(__dirname, '../../docs/assets/example')
const exampleFileName = `${pluginName.replace(/-/g, '.')}.js`
const exampleFile = path.join(exampleDir, exampleFileName)
// Check if a plugin with the same name already exists
if (fs.existsSync(destDir)) {
console.error(`Plugin ${pluginName} already exists. Please choose another name.`);
process.exit(1);
console.error(`Plugin ${pluginName} already exists. Please choose another name.`)
process.exit(1)
}
// Create the destination directory
fs.mkdirSync(destDir, { recursive: true });
fs.mkdirSync(destDir, { recursive: true })
// Read files from the template directory and copy them to the destination directory
function copyTemplateFiles(templateDir, destDir) {
const files = fs.readdirSync(templateDir);
const files = fs.readdirSync(templateDir)
files.forEach((file) => {
const templateFilePath = path.join(templateDir, file);
let destFilePath = path.join(destDir, file);
files.forEach((file) => {
const templateFilePath = path.join(templateDir, file)
let destFilePath = path.join(destDir, file)
const stats = fs.statSync(templateFilePath);
const stats = fs.statSync(templateFilePath)
if (stats.isDirectory()) {
fs.mkdirSync(destFilePath, { recursive: true });
copyTemplateFiles(templateFilePath, destFilePath);
} else {
// Replace placeholders in the file name
destFilePath = destFilePath.replace(/{{name}}/g, pluginName);
if (stats.isDirectory()) {
fs.mkdirSync(destFilePath, { recursive: true })
copyTemplateFiles(templateFilePath, destFilePath)
}
else {
// Replace placeholders in the file name
destFilePath = destFilePath.replace(/\{\{name\}\}/g, pluginName)
let content = fs.readFileSync(templateFilePath, 'utf8');
let content = fs.readFileSync(templateFilePath, 'utf8')
// Replace placeholders in the file content
content = content.replace(/{{name}}/g, pluginName);
content = content.replace(/{{export}}/g, exportNameFull);
// Replace placeholders in the file content
content = content.replace(/\{\{name\}\}/g, pluginName)
content = content.replace(/\{\{export\}\}/g, exportNameFull)
fs.writeFileSync(destFilePath, content);
}
});
fs.writeFileSync(destFilePath, content)
}
})
}
// Create the example file
function createExampleFile() {
const exampleContent = `
const exampleContent = `
// npm i artplayer-plugin-${pluginName}
// import ${exportNameFull} from 'artplayer-plugin-${pluginName}';
@@ -88,18 +90,18 @@ var art = new Artplayer({
}),
],
});
`.trim();
`.trim()
if (!fs.existsSync(exampleDir)) {
fs.mkdirSync(exampleDir, { recursive: true });
}
if (!fs.existsSync(exampleDir)) {
fs.mkdirSync(exampleDir, { recursive: true })
}
fs.writeFileSync(exampleFile, exampleContent);
console.log(`Example file created at ${exampleFile}`);
fs.writeFileSync(exampleFile, exampleContent)
console.log(`Example file created at ${exampleFile}`)
}
// Start copying template files and creating example file
copyTemplateFiles(templateDir, destDir);
createExampleFile();
copyTemplateFiles(templateDir, destDir)
createExampleFile()
console.log(`Plugin ${pluginName} has been successfully created at ${destDir}`);
console.log(`Plugin ${pluginName} has been successfully created at ${destDir}`)
+22 -22
View File
@@ -1,24 +1,24 @@
{
"license": "MIT",
"version": "1.0.0",
"name": "artplayer-plugin-{{name}}",
"types": "types/artplayer-plugin-{{name}}.d.ts",
"description": "{{name}} plugin for ArtPlayer",
"main": "dist/artplayer-plugin-{{name}}.js",
"legacy": "dist/artplayer-plugin-{{name}}.legacy.js",
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
"browserslist": "last 1 Chrome version",
"homepage": "https://artplayer.org",
"repository": {
"type": "git",
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
},
"bugs": {
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
},
"keywords": [
"html5",
"video",
"player"
]
"name": "artplayer-plugin-{{name}}",
"version": "1.0.0",
"description": "{{name}} plugin for ArtPlayer",
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
"license": "MIT",
"homepage": "https://artplayer.org",
"repository": {
"type": "git",
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
},
"bugs": {
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
},
"keywords": [
"html5",
"video",
"player"
],
"main": "dist/artplayer-plugin-{{name}}.js",
"types": "types/artplayer-plugin-{{name}}.d.ts",
"legacy": "dist/artplayer-plugin-{{name}}.legacy.js",
"browserslist": "last 1 Chrome version"
}
+143 -139
View File
@@ -1,171 +1,175 @@
import fs from 'fs';
import cpy from 'cpy';
import path from 'path';
import { glob } from 'glob';
import { removeDir } from './utils.js';
import dotenv from 'dotenv';
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import cpy from 'cpy'
import dotenv from 'dotenv'
import { glob } from 'glob'
import { removeDir } from './utils.js'
dotenv.config();
dotenv.config()
// 配置常量
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000; // 1秒
const MAX_CHARS_PER_REQUEST = 128000;
const BASE_PATH = 'packages/artplayer-vitepress/docs';
const EN_PATH = 'packages/artplayer-vitepress/docs/en';
const COPY_DIRS = ['advanced', 'component', 'plugin', 'start'];
const MAX_RETRIES = 3
const RETRY_DELAY = 1000 // 1秒
const MAX_CHARS_PER_REQUEST = 128000
const BASE_PATH = 'packages/artplayer-vitepress/docs'
const EN_PATH = 'packages/artplayer-vitepress/docs/en'
const COPY_DIRS = ['advanced', 'component', 'plugin', 'start']
function splitMarkdown(text, maxChars) {
const parts = [];
let currentPart = '';
let inCodeBlock = false;
let inSpecialBlock = false;
const parts = []
let currentPart = ''
let inCodeBlock = false
let inSpecialBlock = false
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lines = text.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
// 检测代码块
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock;
}
// 检测特殊块
if (line.trim().startsWith(':::')) {
inSpecialBlock = !inSpecialBlock;
}
// 添加当前行到当前部分
currentPart += line + '\n';
// 检测是否达到字符限制
if (!inCodeBlock && !inSpecialBlock && currentPart.length > maxChars && i < lines.length - 1) {
parts.push(currentPart);
currentPart = '';
}
// 检测代码块
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock
}
// 添加最后一部分
if (currentPart !== '') {
parts.push(currentPart);
// 检测特殊块
if (line.trim().startsWith(':::')) {
inSpecialBlock = !inSpecialBlock
}
return parts;
// 添加当前行到当前部分
currentPart += `${line}\n`
// 检测是否达到字符限制
if (!inCodeBlock && !inSpecialBlock && currentPart.length > maxChars && i < lines.length - 1) {
parts.push(currentPart)
currentPart = ''
}
}
// 添加最后一部分
if (currentPart !== '') {
parts.push(currentPart)
}
return parts
}
async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (retries > 0) {
console.log(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`);
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
return fetchWithRetry(url, options, retries - 1);
}
throw error;
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
}
catch (error) {
if (retries > 0) {
console.log(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`)
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY))
return fetchWithRetry(url, options, retries - 1)
}
throw error
}
}
const translateContent = async (content, targetLanguage) => {
try {
const TRANSLATE_URL = 'https://api.deepseek.com/chat/completions';
const response = await fetchWithRetry(TRANSLATE_URL, {
headers: {
Authorization: `Bearer ${process.env.DEEPL_API_KEY}`,
},
method: 'POST',
body: JSON.stringify({
stream: false,
model: 'deepseek-chat',
messages: [
{
role: 'user',
content: `The following text is written in VitePress's extended Markdown syntax, Please translate it into ${targetLanguage}, and keep the Markdown format and don't add your explanation:\n\n${content}`,
},
],
}),
});
async function translateContent(content, targetLanguage) {
try {
const TRANSLATE_URL = 'https://api.deepseek.com/chat/completions'
const response = await fetchWithRetry(TRANSLATE_URL, {
headers: {
Authorization: `Bearer ${process.env.DEEPL_API_KEY}`,
},
method: 'POST',
body: JSON.stringify({
stream: false,
model: 'deepseek-chat',
messages: [
{
role: 'user',
content: `The following text is written in VitePress's extended Markdown syntax, Please translate it into ${targetLanguage}, and keep the Markdown format and don't add your explanation:\n\n${content}`,
},
],
}),
})
return response.choices[0].message.content;
} catch (error) {
console.error(`Translation failed: ${error.message}`);
return content; // 返回原文作为回退
}
};
return response.choices[0].message.content
}
catch (error) {
console.error(`Translation failed: ${error.message}`)
return content // 返回原文作为回退
}
}
class ProgressTracker {
constructor(totalParts) {
this.totalParts = totalParts;
this.translatedPartsCount = 0;
}
constructor(totalParts) {
this.totalParts = totalParts
this.translatedPartsCount = 0
}
update() {
this.translatedPartsCount++;
const progressPercentage = ((this.translatedPartsCount / this.totalParts) * 100).toFixed(2);
console.log(`Overall progress: ${progressPercentage}% (${this.translatedPartsCount}/${this.totalParts})`);
}
update() {
this.translatedPartsCount++
const progressPercentage = ((this.translatedPartsCount / this.totalParts) * 100).toFixed(2)
console.log(`Overall progress: ${progressPercentage}% (${this.translatedPartsCount}/${this.totalParts})`)
}
}
const translateMarkdownFiles = async (filePaths, targetLanguage, maxCharsPerRequest) => {
// 先计算总共有多少部分需要翻译
let totalParts = 0;
for (const filePath of filePaths) {
const content = fs.readFileSync(filePath, 'utf-8');
totalParts += splitMarkdown(content, maxCharsPerRequest).length;
async function translateMarkdownFiles(filePaths, targetLanguage, maxCharsPerRequest) {
// 先计算总共有多少部分需要翻译
let totalParts = 0
for (const filePath of filePaths) {
const content = fs.readFileSync(filePath, 'utf-8')
totalParts += splitMarkdown(content, maxCharsPerRequest).length
}
console.log(`Starting translation of ${filePaths.length} files, total ${totalParts} parts.`)
const progressTracker = new ProgressTracker(totalParts)
for (const filePath of filePaths) {
console.log(`Translating: ${path.relative(EN_PATH, filePath)}`)
const content = fs.readFileSync(filePath, 'utf-8')
const parts = splitMarkdown(content, maxCharsPerRequest)
const translatedParts = []
for (const part of parts) {
const translatedPart = await translateContent(part, targetLanguage)
translatedParts.push(translatedPart)
progressTracker.update()
}
console.log(`Starting translation of ${filePaths.length} files, total ${totalParts} parts.`);
const progressTracker = new ProgressTracker(totalParts);
for (const filePath of filePaths) {
console.log(`Translating: ${path.relative(EN_PATH, filePath)}`);
const content = fs.readFileSync(filePath, 'utf-8');
const parts = splitMarkdown(content, maxCharsPerRequest);
const translatedParts = [];
for (const part of parts) {
const translatedPart = await translateContent(part, targetLanguage);
translatedParts.push(translatedPart);
progressTracker.update();
}
const translatedContent = translatedParts.join('\n');
fs.writeFileSync(filePath, translatedContent);
}
};
const translatedContent = translatedParts.join('\n')
fs.writeFileSync(filePath, translatedContent)
}
}
async function setupDirectoryStructure() {
// 清理并创建目录结构
removeDir(EN_PATH);
await cpy(path.resolve(BASE_PATH, 'index.md'), path.resolve(EN_PATH));
// 清理并创建目录结构
removeDir(EN_PATH)
await cpy(path.resolve(BASE_PATH, 'index.md'), path.resolve(EN_PATH))
// 并行复制所有目录
await Promise.all(
COPY_DIRS.map((dir) => cpy(path.resolve(BASE_PATH, dir), path.resolve(EN_PATH, dir), { flat: true })),
);
// 并行复制所有目录
await Promise.all(
COPY_DIRS.map(dir => cpy(path.resolve(BASE_PATH, dir), path.resolve(EN_PATH, dir), { flat: true })),
)
}
(async () => {
try {
await setupDirectoryStructure();
const mdsPaths = glob.sync(path.resolve(EN_PATH, '**/*.md'));
await translateMarkdownFiles(mdsPaths, 'English', MAX_CHARS_PER_REQUEST);
console.log('Translation completed successfully!');
} catch (error) {
console.error('Translation failed:', error);
process.exit(1);
}
})();
try {
await setupDirectoryStructure()
const mdsPaths = glob.sync(path.resolve(EN_PATH, '**/*.md'))
await translateMarkdownFiles(mdsPaths, 'English', MAX_CHARS_PER_REQUEST)
console.log('Translation completed successfully!')
}
catch (error) {
console.error('Translation failed:', error)
process.exit(1)
}
})()
+26 -24
View File
@@ -1,30 +1,32 @@
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { glob } from 'glob'
export function getProjects() {
return glob
.sync('packages/*')
.sort()
.filter((item) => !item.endsWith('artplayer-template') && !item.endsWith('artplayer-vitepress'))
.reduce((result, item) => {
const name = item.split(/\/|\\/g).pop();
result[name] = path.resolve(process.cwd(), item);
return result;
}, {});
return glob
.sync('packages/*')
.sort()
.filter(item => !item.endsWith('artplayer-template') && !item.endsWith('artplayer-vitepress'))
.reduce((result, item) => {
const name = item.split(/\/|\\/g).pop()
result[name] = path.resolve(process.cwd(), item)
return result
}, {})
}
export function removeDir(dir) {
if (fs.existsSync(dir)) {
const files = fs.readdirSync(dir);
files.forEach((file) => {
const curPath = path.join(dir, file);
if (fs.statSync(curPath).isDirectory()) {
removeDir(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(dir);
}
if (fs.existsSync(dir)) {
const files = fs.readdirSync(dir)
files.forEach((file) => {
const curPath = path.join(dir, file)
if (fs.statSync(curPath).isDirectory()) {
removeDir(curPath)
}
else {
fs.unlinkSync(curPath)
}
})
fs.rmdirSync(dir)
}
}
+9 -9
View File
@@ -1,12 +1,12 @@
{
"plugins": [
{
"name": "preset-default",
"params": {
"overrides": {
"removeViewBox": false
}
}
"plugins": [
{
"name": "preset-default",
"params": {
"overrides": {
"removeViewBox": false
}
]
}
}
]
}