diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..88aa271 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,47 @@ +name: Deploy VitePress site to Pages + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Build VitePress site + run: npm run docs:build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 813cfc4..fadc527 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,12 @@ dmypy.json *.whl *.tar.gz /tools/upx/ + +# i18n - 不再忽略 .mo 文件,需要提交到仓库以便 CI 使用 +# .mo 文件由 pybabel compile 生成,从 .po 文件编译而来 + +# bv (beads viewer) local config and caches +.bv/ + +# Node.js +node_modules/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a5d922..f9a0f42 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -65,6 +65,31 @@ repos: stages: [pre-push] pass_filenames: false + # i18n 相关检查 + - id: extract-translations + name: Extract i18n messages + entry: bash -c 'pybabel extract -k "_" -k "gettext" -k "ngettext" -o messages.pot remark/ && echo "Translations extracted successfully"' + language: system + types: [python] + stages: [pre-commit] + pass_filenames: false + require_serial: true + + - id: check-i18n-completeness + name: Check i18n completeness + entry: bash -c 'pybabel update -i messages.pot -d locale --check' + language: system + types: [python] + stages: [pre-commit] + pass_filenames: false + + - id: check-po-files + name: Check PO files with polint + entry: bash -c 'if compgen -G "locale/*/LC_MESSAGES/*.po" > /dev/null; then polint locale/*/LC_MESSAGES/*.po || true; fi' + language: system + types: [po] + stages: [pre-commit] + # 全局排除 exclude: | ^(?: diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..9e36cdc --- /dev/null +++ b/README.en.md @@ -0,0 +1,157 @@ +# Windows Folder Remark/Comment Tool + +**[English](README.en.md)** | [中文文档](README.md) + +[![PyPI](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) +[![en](https://img.shields.io/badge/lang-en-blue.svg)](README.en.md) +[![zh](https://img.shields.io/badge/lang-zh-red.svg)](README.md) + +A lightweight CLI tool to add remarks/comments to Windows folders via Desktop.ini. No system residency, no data upload, safe and secure, use it when you need it. Perfect for organizing your files with custom descriptions. + +## ⭐ Star Us + +If you find this tool helpful, please consider giving it a star on GitHub! + +## Why This Tool + +- **Use and Go**: Runs when needed, exits when done — no system residency +- **Safe & Secure**: Completely local operation, no data upload, privacy protected +- **Portable**: Single-file exe packaging, no installation required + +## Features + +- Multi-language character support (UTF-16 encoding) +- Multi-language interface support (English, Chinese) +- Command-line and interactive modes +- Automatic encoding detection and repair +- Automatic update checking to stay current +- Right-click menu integration for quick access +- Single-file exe packaging, no Python environment required + +## Installation + +### Method 1: Using exe file (Recommended) + +Download `windows-folder-remark.exe` from [releases](https://github.com/piratf/windows-folder-remark/releases) and use directly. + +### Method 2: Install from source + +```bash +# Clone repository +git clone https://github.com/piratf/windows-folder-remark.git +cd windows-folder-remark + +# Install dependencies (no external dependencies) +pip install -e . + +# Run +python -m remark.cli --help +``` + +## Usage + +### Command-line Mode + +```bash +# Add remark +windows-folder-remark.exe "C:\MyFolder" "This is my folder" + +# View remark +windows-folder-remark.exe --view "C:\MyFolder" + +# Delete remark +windows-folder-remark.exe --delete "C:\MyFolder" + +# Check updates +windows-folder-remark.exe --update + +# Install right-click menu +windows-folder-remark.exe --install + +# Uninstall right-click menu +windows-folder-remark.exe --uninstall +``` + +### Interactive Mode + +```bash +# Follow prompts after running +windows-folder-remark.exe +``` + +### Right-click Menu (Recommended) + +After installing the right-click menu, you can add remarks directly in File Explorer by right-clicking folders: + +```bash +# Install right-click menu +windows-folder-remark.exe --install +``` + +- **Windows 10**: Right-click folder to see "Add Folder Remark" +- **Windows 11**: Right-click folder → Click "Show more options" → Add Folder Remark + +### Auto Update + +The program automatically checks for updates on exit (once every 24 hours) and prompts if a new version is available. + +You can also manually check for updates: + +```bash +windows-folder-remark.exe --update +``` + +## Encoding Detection + +When viewing remarks with `--view`, if the `desktop.ini` file is not in standard UTF-16 encoding, the tool will prompt you: + +``` +Warning: desktop.ini file encoding is utf-8, not standard UTF-16. +This may cause Chinese and other special characters to display abnormally. +Fix encoding to UTF-16? [Y/n]: +``` + +Select `Y` to automatically fix the encoding. + +## Development + +```bash +# Install development dependencies +pip install -e ".[dev]" + +# Run tests +pytest + +# Code check +ruff check . +ruff format . + +# Type check +mypy remark/ + +# Build exe locally +python -m scripts.build +``` + +## How It Works + +This tool implements folder remarks through these steps: + +1. Create/modify `Desktop.ini` file in the folder +2. Write `[.ShellClassInfo]` section and `InfoTip` property +3. Save file with UTF-16 encoding +4. Set `Desktop.ini` as hidden and system attributes +5. Set folder as read-only (makes Windows read `Desktop.ini`) + +Reference: [Microsoft Official Documentation](https://learn.microsoft.com/en-us/windows/win32/shell/how-to-customize-folders-with-desktop-ini) + +## Notes + +- May take a few minutes to display in File Explorer after modification +- Some file managers may not support folder remarks +- The tool modifies system attributes of folders + +## License + +MIT License diff --git a/readme.md b/README.md similarity index 66% rename from readme.md rename to README.md index 79c8510..a3c0b68 100644 --- a/readme.md +++ b/README.md @@ -1,10 +1,30 @@ -# Windows 文件夹备注工具 +# Windows Folder Remark/Comment Tool - Windows 文件夹备注工具 -一个通过修改 `Desktop.ini` 文件为 Windows 文件夹添加备注/注释的命令行工具。 +**[English Documentation](README.en.md)** | [中文文档](README.md) + +[![PyPI](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) +[![en](https://img.shields.io/badge/lang-en-blue.svg)](README.en.md) +[![zh](https://img.shields.io/badge/lang-zh-red.svg)](README.md) + +A lightweight CLI tool to add remarks/comments to Windows folders via Desktop.ini. No system residency, no data upload, safe and secure, use it when you need it. / 一个轻量级的命令行工具,通过 Desktop.ini 为 Windows 文件夹添加备注/评论。无系统驻留,无数据上传,安全放心,用完即走。 + +**Documentation**: [Full Documentation](https://piratf.github.io/windows-folder-remark/en/) | [完整文档](https://piratf.github.io/windows-folder-remark/zh/) + +## ⭐ 支持 + +如果这个工具对你有帮助,请在 GitHub 上给个 Star! + +## 工具优势 + +- **用完即走**:需要时运行,用完即退出,无系统驻留 +- **安全放心**:完全本地运行,无数据上传,保护隐私 +- **轻量便携**:单文件 exe 打包,无需安装,随处可用 ## 特性 - 支持中文等多语言字符(UTF-16 编码) +- 支持中英文界面切换 - 命令行模式和交互模式 - 自动编码检测和修复 - 自动更新检查,保持最新版本 @@ -71,8 +91,8 @@ windows-folder-remark.exe windows-folder-remark.exe --install ``` -- **Windows 10**: 右键文件夹可直接看到「添加文件夹备注」 -- **Windows 11**: 右键文件夹 → 点击「显示更多选项」→ 添加文件夹备注 +- **Windows 10**:右键文件夹可直接看到「添加文件夹备注」 +- **Windows 11**:右键文件夹 → 点击「显示更多选项」→ 添加文件夹备注 ### 自动更新 diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..db84d47 --- /dev/null +++ b/babel.cfg @@ -0,0 +1,11 @@ +# Babel extraction configuration +# https://babel.pocoo.org/en/latest/messages.html + +# Extract translatable strings from Python files +[python: remark/**/*.py] + +# Keyword list - recognize strings in these function calls as translatable +keywords = _, gettext, ngettext + +# Encoding +encoding = utf-8 diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs new file mode 100644 index 0000000..024c6ae --- /dev/null +++ b/docs/.vitepress/config.mjs @@ -0,0 +1,52 @@ +import { defineConfig } from 'vitepress' + +export default defineConfig({ + title: 'Windows Folder Remark Tool', + description: 'A lightweight CLI tool to add remarks/comments to Windows folders via Desktop.ini', + base: '/windows-folder-remark/', + lang: 'en-US', + + locales: { + root: { + label: 'English', + lang: 'en-US' + }, + zh: { + label: '简体中文', + lang: 'zh-CN', + link: '/zh/' + } + }, + + themeConfig: { + nav: () => [ + { text: 'Guide', link: '/en/guide/' }, + { text: '中文', link: '/zh/' } + ], + + sidebar: { + '/en/': [ + { + text: 'Guide', + items: [ + { text: 'Introduction', link: '/en/' }, + { text: 'Getting Started', link: '/en/guide/getting-started' }, + { text: 'Usage', link: '/en/guide/usage' }, + { text: 'API Reference', link: '/en/guide/api' } + ] + } + ], + '/zh/': [ + { + text: '指南', + items: [ + { text: '介绍', link: '/zh/' }, + { text: '快速开始', link: '/zh/guide/getting-started' }, + { text: '使用方法', link: '/zh/guide/usage' }, + { text: 'API 参考', link: '/zh/guide/api' } + ] + } + ] + } + } +}) diff --git a/docs/en/guide/api.md b/docs/en/guide/api.md new file mode 100644 index 0000000..337445c --- /dev/null +++ b/docs/en/guide/api.md @@ -0,0 +1,21 @@ +# API Reference + +## Command-line Arguments + +| Argument | Short | Description | +|---|---|---| +| `--help` | `-h` | Show help information | +| `--install` | | Install right-click menu | +| `--uninstall` | | Uninstall right-click menu | +| `--update` | | Check for updates | +| `--view ` | | View folder remark | +| `--delete ` | | Delete folder remark | +| `--gui ` | | GUI mode | +| `--lang ` | `-L` | Set language (en, zh) | + +## Exit Codes + +| Code | Description | +|---|---| +| 0 | Success | +| 1 | Error | diff --git a/docs/en/guide/getting-started.md b/docs/en/guide/getting-started.md new file mode 100644 index 0000000..a2d9315 --- /dev/null +++ b/docs/en/guide/getting-started.md @@ -0,0 +1,33 @@ +# Getting Started + +## Download + +Download `windows-folder-remark.exe` from [GitHub Releases](https://github.com/piratf/windows-folder-remark/releases). + +## Basic Usage + +### Add Remark + +```bash +windows-folder-remark.exe "C:\MyFolder" "This is my folder" +``` + +### View Remark + +```bash +windows-folder-remark.exe --view "C:\MyFolder" +``` + +### Delete Remark + +```bash +windows-folder-remark.exe --delete "C:\MyFolder" +``` + +## Install Right-click Menu + +```bash +windows-folder-remark.exe --install +``` + +After installation, you can add remarks directly in File Explorer by right-clicking folders. diff --git a/docs/en/guide/usage.md b/docs/en/guide/usage.md new file mode 100644 index 0000000..50a7883 --- /dev/null +++ b/docs/en/guide/usage.md @@ -0,0 +1,40 @@ +# Usage + +## Command-line Mode + +```bash +# Add remark +windows-folder-remark.exe "C:\MyFolder" "My Folder" + +# View remark +windows-folder-remark.exe --view "C:\MyFolder" + +# Delete remark +windows-folder-remark.exe --delete "C:\MyFolder" +``` + +## Interactive Mode + +```bash +windows-folder-remark.exe +``` + +## Language Switch + +```bash +# Use English +windows-folder-remark.exe --lang en + +# Use Chinese +windows-folder-remark.exe --lang zh +``` + +## Auto Update + +The program automatically checks for updates on exit (once every 24 hours). + +Manual update check: + +```bash +windows-folder-remark.exe --update +``` diff --git a/docs/en/index.md b/docs/en/index.md new file mode 100644 index 0000000..2e7f6e8 --- /dev/null +++ b/docs/en/index.md @@ -0,0 +1,33 @@ +--- +layout: home + +hero: + name: Windows Folder Remark Tool + text: A Lightweight CLI Tool for Windows Folder Remarks + tagline: Add remarks/comments to Windows folders via Desktop.ini + actions: + - theme: brand + text: Get Started + link: /en/guide/getting-started + - theme: alt + text: GitHub + link: https://github.com/piratf/windows-folder-remark + +features: + - title: Use and Go + details: Runs when needed, exits when done — no system residency + - title: Safe & Secure + details: Completely local operation, no data upload, privacy protected + - title: Portable + details: Single-file exe packaging, no installation required + - title: Multi-language Support + details: English and Chinese interface with auto language detection + - title: UTF-16 Encoding + details: Full support for Chinese and other special characters + - title: Auto Update + details: Built-in update checking to stay current +--- + +## ⭐ Star Us + +If you find this tool helpful, please give it a star on [GitHub](https://github.com/piratf/windows-folder-remark)! diff --git a/docs/zh/guide/api.md b/docs/zh/guide/api.md new file mode 100644 index 0000000..e5c7ced --- /dev/null +++ b/docs/zh/guide/api.md @@ -0,0 +1,21 @@ +# API 参考 + +## 命令行参数 + +| 参数 | 简写 | 说明 | +|---|---|---| +| `--help` | `-h` | 显示帮助信息 | +| `--install` | | 安装右键菜单 | +| `--uninstall` | | 卸载右键菜单 | +| `--update` | | 检查更新 | +| `--view ` | | 查看文件夹备注 | +| `--delete ` | | 删除文件夹备注 | +| `--gui ` | | GUI 模式 | +| `--lang ` | `-L` | 设置语言 (en, zh) | + +## 退出码 + +| 代码 | 说明 | +|---|---| +| 0 | 成功 | +| 1 | 错误 | diff --git a/docs/zh/guide/getting-started.md b/docs/zh/guide/getting-started.md new file mode 100644 index 0000000..415329a --- /dev/null +++ b/docs/zh/guide/getting-started.md @@ -0,0 +1,33 @@ +# 快速开始 + +## 下载 + +从 [GitHub Releases](https://github.com/piratf/windows-folder-remark/releases) 下载 `windows-folder-remark.exe`。 + +## 基本使用 + +### 添加备注 + +```bash +windows-folder-remark.exe "C:\MyFolder" "这是我的文件夹" +``` + +### 查看备注 + +```bash +windows-folder-remark.exe --view "C:\MyFolder" +``` + +### 删除备注 + +```bash +windows-folder-remark.exe --delete "C:\MyFolder" +``` + +## 安装右键菜单 + +```bash +windows-folder-remark.exe --install +``` + +安装后可以在文件资源管理器中右键文件夹直接添加备注。 diff --git a/docs/zh/guide/usage.md b/docs/zh/guide/usage.md new file mode 100644 index 0000000..da64470 --- /dev/null +++ b/docs/zh/guide/usage.md @@ -0,0 +1,40 @@ +# 使用方法 + +## 命令行模式 + +```bash +# 添加备注 +windows-folder-remark.exe "C:\MyFolder" "我的文件夹" + +# 查看备注 +windows-folder-remark.exe --view "C:\MyFolder" + +# 删除备注 +windows-folder-remark.exe --delete "C:\MyFolder" +``` + +## 交互模式 + +```bash +windows-folder-remark.exe +``` + +## 语言切换 + +```bash +# 使用中文 +windows-folder-remark.exe --lang zh + +# 使用英文 +windows-folder-remark.exe --lang en +``` + +## 自动更新 + +程序会在退出时自动检查更新(每 24 小时一次)。 + +手动检查更新: + +```bash +windows-folder-remark.exe --update +``` diff --git a/docs/zh/index.md b/docs/zh/index.md new file mode 100644 index 0000000..c4d7b59 --- /dev/null +++ b/docs/zh/index.md @@ -0,0 +1,33 @@ +--- +layout: home + +hero: + name: Windows Folder Remark Tool + text: 轻量级 Windows 文件夹备注工具 + tagline: 通过 Desktop.ini 为 Windows 文件夹添加备注/评论 + actions: + - theme: brand + text: 快速开始 + link: /zh/guide/getting-started + - theme: alt + text: GitHub + link: https://github.com/piratf/windows-folder-remark + +features: + - title: 用完即走 + details: 需要时运行,用完即退出,无系统驻留 + - title: 安全放心 + details: 完全本地运行,无数据上传,保护隐私 + - title: 轻量便携 + details: 单文件 exe 打包,无需安装,随处可用 + - title: 多语言支持 + details: 支持中英文界面,自动检测系统语言 + - title: UTF-16 编码 + details: 支持中文等多语言字符 + - title: 自动更新 + details: 内置更新检查,保持最新版本 +--- + +## ⭐ 支持 + +如果这个工具对你有帮助,请在 [GitHub](https://github.com/piratf/windows-folder-remark) 上给个 Star! diff --git a/locale/zh/LC_MESSAGES/messages.mo b/locale/zh/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..95fb5d6 Binary files /dev/null and b/locale/zh/LC_MESSAGES/messages.mo differ diff --git a/locale/zh/LC_MESSAGES/messages.po b/locale/zh/LC_MESSAGES/messages.po new file mode 100644 index 0000000..b67eeb1 --- /dev/null +++ b/locale/zh/LC_MESSAGES/messages.po @@ -0,0 +1,520 @@ +# Chinese translations for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-01-29 22:34+0800\n" +"PO-Revision-Date: 2026-01-29 22:35+0800\n" +"Last-Translator: FULL NAME \n" +"Language: zh\n" +"Language-Team: zh \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: remark/cli/commands.py:55 +#, python-brace-format +msgid "Path does not exist: {path}" +msgstr "路径不存在: {path}" + +#: remark/cli/commands.py:58 +#, python-brace-format +msgid "Path is not a folder: {path}" +msgstr "路径不是文件夹: {path}" + +#: remark/cli/commands.py:80 +#, python-brace-format +msgid "Current version: {version}" +msgstr "当前版本: {version}" + +#: remark/cli/commands.py:81 +msgid "Checking for updates..." +msgstr "正在检查更新..." + +#: remark/cli/commands.py:86 +#, python-brace-format +msgid "" +"\n" +"New version found: {tag_name}" +msgstr "" +"\n" +"发现新版本: {tag_name}" + +#: remark/cli/commands.py:87 remark/cli/commands.py:115 +#, python-brace-format +msgid "Update notes: {notes}" +msgstr "更新说明: {notes}" + +#: remark/cli/commands.py:88 remark/cli/commands.py:116 +#, python-brace-format +msgid "Full changelog: {url}" +msgstr "完整更新日志: {url}" + +#: remark/cli/commands.py:89 +msgid "" +"\n" +"Update now? [Y/n]: " +msgstr "" +"\n" +"是否立即更新? [Y/n]: " + +#: remark/cli/commands.py:94 +msgid "Already at the latest version" +msgstr "已是最新版本" + +#: remark/cli/commands.py:111 +#, python-brace-format +msgid "" +"\n" +"New version available: {tag_name} (Current version: {version})" +msgstr "" +"\n" +"发现新版本: {tag_name} (当前版本: {version})" + +#: remark/cli/commands.py:117 +msgid "Update now? [Y/n]: " +msgstr "是否立即更新? [Y/n]: " + +#: remark/cli/commands.py:124 +msgid "Downloading new version..." +msgstr "正在下载新版本..." + +#: remark/cli/commands.py:131 +msgid "Download complete, preparing update..." +msgstr "下载完成,准备更新..." + +#: remark/cli/commands.py:135 +msgid "Update program has started, the application will exit..." +msgstr "更新程序已启动,程序即将退出..." + +#: remark/cli/commands.py:136 +msgid "Please wait a few moments, the update will complete automatically." +msgstr "请等待几秒钟,更新将自动完成。" + +#: remark/cli/commands.py:142 +msgid "Download failed: Connection reset by server" +msgstr "下载失败:连接被服务器断开" + +#: remark/cli/commands.py:143 +msgid "Please try again later, or visit the following link to download manually:" +msgstr "请稍后重试,或访问以下链接手动下载:" + +#: remark/cli/commands.py:146 +msgid "Download failed: Request timeout" +msgstr "下载失败:请求超时" + +#: remark/cli/commands.py:147 remark/cli/commands.py:151 +msgid "" +"Please check your network connection, or visit the following link to " +"download manually:" +msgstr "请检查网络连接,或访问以下链接手动下载:" + +#: remark/cli/commands.py:150 +msgid "Download failed: Unable to connect to server" +msgstr "下载失败:无法连接到服务器" + +#: remark/cli/commands.py:154 +msgid "Download failed, please check your network or download manually" +msgstr "下载失败,请检查网络连接或手动下载更新" + +#: remark/cli/commands.py:157 +#, python-brace-format +msgid "Update failed: {error}" +msgstr "更新失败: {error}" + +#: remark/cli/commands.py:158 +#, python-brace-format +msgid "Manual download: {url}" +msgstr "手动下载: {url}" + +#: remark/cli/commands.py:175 +msgid "Right-click menu installed successfully" +msgstr "右键菜单安装成功" + +#: remark/cli/commands.py:177 +msgid "Usage Instructions:" +msgstr "使用说明:" + +#: remark/cli/commands.py:178 +msgid " Windows 10: Right-click folder to see 'Add Folder Remark'" +msgstr " Windows 10: 右键文件夹可直接看到「添加文件夹备注」" + +#: remark/cli/commands.py:179 +msgid "" +" Windows 11: Right-click folder → Click 'Show more options' → Add Folder" +" Remark" +msgstr " Windows 11: 右键文件夹 → 点击「显示更多选项」→ 添加文件夹备注" + +#: remark/cli/commands.py:182 +msgid "Right-click menu installation failed" +msgstr "右键菜单安装失败" + +#: remark/cli/commands.py:188 +msgid "Right-click menu uninstalled" +msgstr "右键菜单已卸载" + +#: remark/cli/commands.py:191 +msgid "Right-click menu uninstallation failed" +msgstr "右键菜单卸载失败" + +#: remark/cli/commands.py:218 remark/storage/desktop_ini.py:309 +#, python-brace-format +msgid "Warning: desktop.ini file encoding is {encoding}, not standard UTF-16." +msgstr "警告: desktop.ini 文件编码为 {encoding},不是标准的 UTF-16。" + +#: remark/cli/commands.py:219 remark/storage/desktop_ini.py:310 +msgid "unknown" +msgstr "未知" + +#: remark/cli/commands.py:221 +msgid "This may cause Chinese and other special characters to display abnormally." +msgstr "这可能导致中文等特殊字符显示异常。" + +#: remark/cli/commands.py:225 +msgid "Fix encoding to UTF-16? [Y/n]: " +msgstr "是否修复编码为 UTF-16?[Y/n]: " + +#: remark/cli/commands.py:228 +msgid "Fixed to UTF-16 encoding" +msgstr "已修复为 UTF-16 编码" + +#: remark/cli/commands.py:230 +msgid "Failed to fix encoding" +msgstr "修复失败" + +#: remark/cli/commands.py:233 +msgid "Skip encoding fix" +msgstr "跳过编码修复" + +#: remark/cli/commands.py:236 remark/storage/desktop_ini.py:335 +msgid "Please enter Y or n" +msgstr "请输入 Y 或 n" + +#: remark/cli/commands.py:241 +#, python-brace-format +msgid "Current remark: {remark}" +msgstr "当前备注: {remark}" + +#: remark/cli/commands.py:243 remark/core/folder_handler.py:85 +msgid "This folder has no remark" +msgstr "该文件夹没有备注" + +#: remark/cli/commands.py:248 +#, python-brace-format +msgid "Windows Folder Remark Tool v{version}" +msgstr "Windows 文件夹备注工具 v{version}" + +#: remark/cli/commands.py:249 +msgid "Tip: Press Ctrl + C to exit" +msgstr "提示: 按 Ctrl + C 退出程序" + +#: remark/cli/commands.py:251 +msgid "Enter folder path (or drag here): " +msgstr "请输入文件夹路径(或拖动到这里): " + +#: remark/cli/commands.py:252 +msgid "Enter remark:" +msgstr "请输入备注:" + +#: remark/cli/commands.py:259 +msgid "Path does not exist, please re-enter" +msgstr "路径不存在,请重新输入" + +#: remark/cli/commands.py:263 +msgid "This is a 'file', currently only supports adding remarks to 'folders'" +msgstr "这是一个「文件」,当前仅支持为「文件夹」添加备注" + +#: remark/cli/commands.py:268 +msgid "Remark cannot be empty" +msgstr "备注不要为空哦" + +#: remark/cli/commands.py:274 +msgid " ❤ Thank you for using" +msgstr " ❤ 感谢使用" + +#: remark/cli/commands.py:276 +msgid "Continue processing or press Ctrl + C to exit" +msgstr "继续处理或按 Ctrl + C 退出程序" + +#: remark/cli/commands.py:280 +msgid "Windows Folder Remark Tool" +msgstr "Windows 文件夹备注工具" + +#: remark/cli/commands.py:281 +msgid "Usage:" +msgstr "使用方法:" + +#: remark/cli/commands.py:282 +msgid " Interactive mode: python remark.py" +msgstr " 交互模式: python remark.py" + +#: remark/cli/commands.py:283 +msgid " Command line mode: python remark.py [options] [arguments]" +msgstr " 命令行模式: python remark.py [选项] [参数]" + +#: remark/cli/commands.py:284 +msgid "Options:" +msgstr "选项:" + +#: remark/cli/commands.py:285 +msgid " --install Install right-click menu" +msgstr " --install 安装右键菜单" + +#: remark/cli/commands.py:286 +msgid " --uninstall Uninstall right-click menu" +msgstr " --uninstall 卸载右键菜单" + +#: remark/cli/commands.py:287 +msgid " --update Check for updates" +msgstr " --update 检查更新" + +#: remark/cli/commands.py:288 +msgid " --gui GUI mode (called from right-click menu)" +msgstr " --gui <路径> GUI 模式(右键菜单调用)" + +#: remark/cli/commands.py:289 +msgid " --delete Delete remark" +msgstr " --delete <路径> 删除备注" + +#: remark/cli/commands.py:290 +msgid " --view View remark" +msgstr " --view <路径> 查看备注" + +#: remark/cli/commands.py:291 +msgid " --help, -h Show help information" +msgstr " --help, -h 显示帮助信息" + +#: remark/cli/commands.py:292 +msgid "Examples:" +msgstr "示例:" + +#: remark/cli/commands.py:293 +msgid " [Add remark] python remark.py \"C:\\\\MyFolder\" \"My Folder\"" +msgstr " [添加备注] python remark.py \"C:\\\\MyFolder\" \"这是我的文件夹\"" + +#: remark/cli/commands.py:294 +msgid " [Delete remark] python remark.py --delete \"C:\\\\MyFolder\"" +msgstr " [删除备注] python remark.py --delete \"C:\\\\MyFolder\"" + +#: remark/cli/commands.py:295 +msgid " [View current remark] python remark.py --view \"C:\\\\MyFolder\"" +msgstr " [查看当前备注] python remark.py --view \"C:\\\\MyFolder\"" + +#: remark/cli/commands.py:296 +msgid " [Install right-click menu] python remark.py --install" +msgstr " [安装右键菜单] python remark.py --install" + +#: remark/cli/commands.py:297 +msgid " [Check for updates] python remark.py --update" +msgstr " [检查更新] python remark.py --update" + +#: remark/cli/commands.py:313 +msgid "Error: Path does not exist or not quoted" +msgstr "错误: 路径不存在或未使用引号" + +#: remark/cli/commands.py:314 +msgid "Hint: Use quotes when path contains spaces" +msgstr "提示: 路径包含空格时请使用引号" + +#: remark/cli/commands.py:315 +msgid " windows-folder-remark \"C:\\\\My Documents\" \"Remark content\"" +msgstr " windows-folder-remark \"C:\\\\My Documents\" \"备注内容\"" + +#: remark/cli/commands.py:320 +#, python-brace-format +msgid "Detected path: {path}" +msgstr "检测到路径: {path}" + +#: remark/cli/commands.py:323 +msgid "Error: This is a file, the tool can only set remarks for folders" +msgstr "错误: 这是一个文件,工具只能为文件夹设置备注" + +#: remark/cli/commands.py:328 +#, python-brace-format +msgid "Remark content: {remark}" +msgstr "备注内容: {remark}" + +#: remark/cli/commands.py:330 +msgid "(Will view existing remark)" +msgstr "(将查看现有备注)" + +#: remark/cli/commands.py:332 +msgid "Continue? [Y/n]: " +msgstr "是否继续? [Y/n]: " + +#: remark/cli/commands.py:338 +msgid "Detected multiple possible paths, please select:" +msgstr "检测到多个可能的路径,请选择:" + +#: remark/cli/commands.py:340 +msgid " [file]" +msgstr " [文件]" + +#: remark/cli/commands.py:341 +#, python-brace-format +msgid "" +"\n" +"[{index}] Path: {path}{type_mark}" +msgstr "" +"\n" +"[{index}] 路径: {path}{type_mark}" + +#: remark/cli/commands.py:343 +#, python-brace-format +msgid " Remaining remarks: {remarks}" +msgstr " 剩余备注: {remarks}" + +#: remark/cli/commands.py:345 +msgid " (Will view existing remark)" +msgstr " (将查看现有备注)" + +#: remark/cli/commands.py:346 +msgid "" +"\n" +"[0] Cancel" +msgstr "" +"\n" +"[0] 取消" + +#: remark/cli/commands.py:349 +#, python-brace-format +msgid "" +"\n" +"Please select [0-{max}]: " +msgstr "" +"\n" +"请选择 [0-{max}]: " + +#: remark/cli/commands.py:355 +msgid "" +"\n" +"Error: This is a file, the tool can only set remarks for folders, please " +"reselect" +msgstr "" +"\n" +"错误: 这是一个文件,工具只能为文件夹设置备注,请重新选择" + +#: remark/cli/commands.py:358 +msgid "Invalid selection, please try again" +msgstr "无效选择,请重试" + +#: remark/cli/commands.py:419 +msgid "" +"\n" +"Operation cancelled" +msgstr "" +"\n" +"操作已取消" + +#: remark/cli/commands.py:422 +#, python-brace-format +msgid "An error occurred: {error}" +msgstr "发生错误: {error}" + +#: remark/core/folder_handler.py:24 +#, python-brace-format +msgid "Path is not a folder: {folder_path}" +msgstr "路径不是文件夹: {folder_path}" + +#: remark/core/folder_handler.py:29 +#, python-brace-format +msgid "Remark length exceeds limit, maximum length is {length} characters" +msgstr "备注长度超过限制,最大长度为 {length} 个字符" + +#: remark/core/folder_handler.py:47 remark/core/folder_handler.py:90 +msgid "Failed to clear file attributes" +msgstr "清除文件属性失败" + +#: remark/core/folder_handler.py:52 +msgid "Failed to write desktop.ini" +msgstr "写入 desktop.ini 失败" + +#: remark/core/folder_handler.py:57 +msgid "Failed to set file attributes" +msgstr "设置文件属性失败" + +#: remark/core/folder_handler.py:62 +msgid "Failed to set folder attributes" +msgstr "设置文件夹属性失败" + +#: remark/core/folder_handler.py:66 +#, python-brace-format +msgid "Remark [{remark}] has been set for folder [{folder_path}]" +msgstr "已经为文件夹 [{folder_path}] 设置备注 [{remark}]" + +#: remark/core/folder_handler.py:70 +msgid "Remark added successfully, may take a few minutes to display" +msgstr "备注添加成功,可能需要过几分钟才会显示" + +#: remark/core/folder_handler.py:73 +#, python-brace-format +msgid "Failed to set remark: {error}" +msgstr "设置备注失败: {error}" + +#: remark/core/folder_handler.py:95 +msgid "Failed to remove remark" +msgstr "移除备注失败" + +#: remark/core/folder_handler.py:102 +msgid "Failed to restore file attributes" +msgstr "恢复文件属性失败" + +#: remark/core/folder_handler.py:105 +msgid "Remark deleted successfully" +msgstr "备注删除成功" + +#: remark/storage/desktop_ini.py:313 +msgid "This file needs to be converted to UTF-16 encoding before modification." +msgstr "修改此文件前需要先转换为 UTF-16 编码。" + +#: remark/storage/desktop_ini.py:314 +msgid "" +"The original content will be preserved, only the encoding format will " +"change." +msgstr "原内容会被保留,仅改变编码格式。" + +#: remark/storage/desktop_ini.py:321 +msgid "" +"\n" +"Current file content:" +msgstr "" +"\n" +"当前文件内容:" + +#: remark/storage/desktop_ini.py:328 +msgid "" +"\n" +"Convert to UTF-16 encoding and continue? [Y/n]: " +msgstr "" +"\n" +"是否转换为 UTF-16 编码后继续?[Y/n]: " + +#: remark/storage/desktop_ini.py:332 remark/storage/desktop_ini.py:347 +msgid "Operation cancelled." +msgstr "操作已取消。" + +#: remark/storage/desktop_ini.py:341 +msgid "Converted to UTF-16 encoding." +msgstr "已转换为 UTF-16 编码。" + +#: remark/storage/desktop_ini.py:346 +#, python-brace-format +msgid "Conversion failed: {error}" +msgstr "转换失败: {error}" + +#: remark/utils/platform.py:13 +msgid "" +"Error: This tool adds remarks to files/folders on Windows, other systems " +"are not supported." +msgstr "错误: 此工具为 Windows 系统中的文件/文件夹添加备注,暂不支持其他系统。" + +#: remark/utils/platform.py:14 +#, python-brace-format +msgid "Current system: {system}" +msgstr "当前系统: {system}" diff --git a/messages.pot b/messages.pot new file mode 100644 index 0000000..a572213 --- /dev/null +++ b/messages.pot @@ -0,0 +1,500 @@ +# Translations template for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-01-29 22:53+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: remark/cli/commands.py:55 +#, python-brace-format +msgid "Path does not exist: {path}" +msgstr "" + +#: remark/cli/commands.py:58 +#, python-brace-format +msgid "Path is not a folder: {path}" +msgstr "" + +#: remark/cli/commands.py:80 +#, python-brace-format +msgid "Current version: {version}" +msgstr "" + +#: remark/cli/commands.py:81 +msgid "Checking for updates..." +msgstr "" + +#: remark/cli/commands.py:86 +#, python-brace-format +msgid "" +"\n" +"New version found: {tag_name}" +msgstr "" + +#: remark/cli/commands.py:87 remark/cli/commands.py:115 +#, python-brace-format +msgid "Update notes: {notes}" +msgstr "" + +#: remark/cli/commands.py:88 remark/cli/commands.py:116 +#, python-brace-format +msgid "Full changelog: {url}" +msgstr "" + +#: remark/cli/commands.py:89 +msgid "" +"\n" +"Update now? [Y/n]: " +msgstr "" + +#: remark/cli/commands.py:94 +msgid "Already at the latest version" +msgstr "" + +#: remark/cli/commands.py:111 +#, python-brace-format +msgid "" +"\n" +"New version available: {tag_name} (Current version: {version})" +msgstr "" + +#: remark/cli/commands.py:117 +msgid "Update now? [Y/n]: " +msgstr "" + +#: remark/cli/commands.py:124 +msgid "Downloading new version..." +msgstr "" + +#: remark/cli/commands.py:131 +msgid "Download complete, preparing update..." +msgstr "" + +#: remark/cli/commands.py:135 +msgid "Update program has started, the application will exit..." +msgstr "" + +#: remark/cli/commands.py:136 +msgid "Please wait a few moments, the update will complete automatically." +msgstr "" + +#: remark/cli/commands.py:142 +msgid "Download failed: Connection reset by server" +msgstr "" + +#: remark/cli/commands.py:143 +msgid "Please try again later, or visit the following link to download manually:" +msgstr "" + +#: remark/cli/commands.py:146 +msgid "Download failed: Request timeout" +msgstr "" + +#: remark/cli/commands.py:147 remark/cli/commands.py:151 +msgid "" +"Please check your network connection, or visit the following link to " +"download manually:" +msgstr "" + +#: remark/cli/commands.py:150 +msgid "Download failed: Unable to connect to server" +msgstr "" + +#: remark/cli/commands.py:154 +msgid "Download failed, please check your network or download manually" +msgstr "" + +#: remark/cli/commands.py:157 +#, python-brace-format +msgid "Update failed: {error}" +msgstr "" + +#: remark/cli/commands.py:158 +#, python-brace-format +msgid "Manual download: {url}" +msgstr "" + +#: remark/cli/commands.py:175 +msgid "Right-click menu installed successfully" +msgstr "" + +#: remark/cli/commands.py:177 +msgid "Usage Instructions:" +msgstr "" + +#: remark/cli/commands.py:178 +msgid " Windows 10: Right-click folder to see 'Add Folder Remark'" +msgstr "" + +#: remark/cli/commands.py:179 +msgid "" +" Windows 11: Right-click folder → Click 'Show more options' → Add Folder" +" Remark" +msgstr "" + +#: remark/cli/commands.py:182 +msgid "Right-click menu installation failed" +msgstr "" + +#: remark/cli/commands.py:188 +msgid "Right-click menu uninstalled" +msgstr "" + +#: remark/cli/commands.py:191 +msgid "Right-click menu uninstallation failed" +msgstr "" + +#: remark/cli/commands.py:218 remark/storage/desktop_ini.py:309 +#, python-brace-format +msgid "Warning: desktop.ini file encoding is {encoding}, not standard UTF-16." +msgstr "" + +#: remark/cli/commands.py:219 remark/storage/desktop_ini.py:310 +msgid "unknown" +msgstr "" + +#: remark/cli/commands.py:221 +msgid "This may cause Chinese and other special characters to display abnormally." +msgstr "" + +#: remark/cli/commands.py:225 +msgid "Fix encoding to UTF-16? [Y/n]: " +msgstr "" + +#: remark/cli/commands.py:228 +msgid "Fixed to UTF-16 encoding" +msgstr "" + +#: remark/cli/commands.py:230 +msgid "Failed to fix encoding" +msgstr "" + +#: remark/cli/commands.py:233 +msgid "Skip encoding fix" +msgstr "" + +#: remark/cli/commands.py:236 remark/storage/desktop_ini.py:335 +msgid "Please enter Y or n" +msgstr "" + +#: remark/cli/commands.py:241 +#, python-brace-format +msgid "Current remark: {remark}" +msgstr "" + +#: remark/cli/commands.py:243 remark/core/folder_handler.py:85 +msgid "This folder has no remark" +msgstr "" + +#: remark/cli/commands.py:248 +#, python-brace-format +msgid "Windows Folder Remark Tool v{version}" +msgstr "" + +#: remark/cli/commands.py:249 +msgid "Tip: Press Ctrl + C to exit" +msgstr "" + +#: remark/cli/commands.py:251 +msgid "Enter folder path (or drag here): " +msgstr "" + +#: remark/cli/commands.py:252 +msgid "Enter remark:" +msgstr "" + +#: remark/cli/commands.py:259 +msgid "Path does not exist, please re-enter" +msgstr "" + +#: remark/cli/commands.py:263 +msgid "This is a 'file', currently only supports adding remarks to 'folders'" +msgstr "" + +#: remark/cli/commands.py:268 +msgid "Remark cannot be empty" +msgstr "" + +#: remark/cli/commands.py:274 +msgid " ❤ Thank you for using" +msgstr "" + +#: remark/cli/commands.py:276 +msgid "Continue processing or press Ctrl + C to exit" +msgstr "" + +#: remark/cli/commands.py:280 +msgid "Windows Folder Remark Tool" +msgstr "" + +#: remark/cli/commands.py:281 +msgid "Usage:" +msgstr "" + +#: remark/cli/commands.py:282 +msgid " Interactive mode: python remark.py" +msgstr "" + +#: remark/cli/commands.py:283 +msgid " Command line mode: python remark.py [options] [arguments]" +msgstr "" + +#: remark/cli/commands.py:284 +msgid "Options:" +msgstr "" + +#: remark/cli/commands.py:285 +msgid " --install Install right-click menu" +msgstr "" + +#: remark/cli/commands.py:286 +msgid " --uninstall Uninstall right-click menu" +msgstr "" + +#: remark/cli/commands.py:287 +msgid " --update Check for updates" +msgstr "" + +#: remark/cli/commands.py:288 +msgid " --gui GUI mode (called from right-click menu)" +msgstr "" + +#: remark/cli/commands.py:289 +msgid " --delete Delete remark" +msgstr "" + +#: remark/cli/commands.py:290 +msgid " --view View remark" +msgstr "" + +#: remark/cli/commands.py:291 +msgid " --help, -h Show help information" +msgstr "" + +#: remark/cli/commands.py:292 +msgid "Examples:" +msgstr "" + +#: remark/cli/commands.py:293 +msgid " [Add remark] python remark.py \"C:\\\\MyFolder\" \"My Folder\"" +msgstr "" + +#: remark/cli/commands.py:294 +msgid " [Delete remark] python remark.py --delete \"C:\\\\MyFolder\"" +msgstr "" + +#: remark/cli/commands.py:295 +msgid " [View current remark] python remark.py --view \"C:\\\\MyFolder\"" +msgstr "" + +#: remark/cli/commands.py:296 +msgid " [Install right-click menu] python remark.py --install" +msgstr "" + +#: remark/cli/commands.py:297 +msgid " [Check for updates] python remark.py --update" +msgstr "" + +#: remark/cli/commands.py:313 +msgid "Error: Path does not exist or not quoted" +msgstr "" + +#: remark/cli/commands.py:314 +msgid "Hint: Use quotes when path contains spaces" +msgstr "" + +#: remark/cli/commands.py:315 +msgid " windows-folder-remark \"C:\\\\My Documents\" \"Remark content\"" +msgstr "" + +#: remark/cli/commands.py:320 +#, python-brace-format +msgid "Detected path: {path}" +msgstr "" + +#: remark/cli/commands.py:323 +msgid "Error: This is a file, the tool can only set remarks for folders" +msgstr "" + +#: remark/cli/commands.py:328 +#, python-brace-format +msgid "Remark content: {remark}" +msgstr "" + +#: remark/cli/commands.py:330 +msgid "(Will view existing remark)" +msgstr "" + +#: remark/cli/commands.py:332 +msgid "Continue? [Y/n]: " +msgstr "" + +#: remark/cli/commands.py:338 +msgid "Detected multiple possible paths, please select:" +msgstr "" + +#: remark/cli/commands.py:340 +msgid " [file]" +msgstr "" + +#: remark/cli/commands.py:341 +#, python-brace-format +msgid "" +"\n" +"[{index}] Path: {path}{type_mark}" +msgstr "" + +#: remark/cli/commands.py:343 +#, python-brace-format +msgid " Remaining remarks: {remarks}" +msgstr "" + +#: remark/cli/commands.py:345 +msgid " (Will view existing remark)" +msgstr "" + +#: remark/cli/commands.py:346 +msgid "" +"\n" +"[0] Cancel" +msgstr "" + +#: remark/cli/commands.py:349 +#, python-brace-format +msgid "" +"\n" +"Please select [0-{max}]: " +msgstr "" + +#: remark/cli/commands.py:355 +msgid "" +"\n" +"Error: This is a file, the tool can only set remarks for folders, please " +"reselect" +msgstr "" + +#: remark/cli/commands.py:358 +msgid "Invalid selection, please try again" +msgstr "" + +#: remark/cli/commands.py:419 +msgid "" +"\n" +"Operation cancelled" +msgstr "" + +#: remark/cli/commands.py:422 +#, python-brace-format +msgid "An error occurred: {error}" +msgstr "" + +#: remark/core/folder_handler.py:24 +#, python-brace-format +msgid "Path is not a folder: {folder_path}" +msgstr "" + +#: remark/core/folder_handler.py:29 +#, python-brace-format +msgid "Remark length exceeds limit, maximum length is {length} characters" +msgstr "" + +#: remark/core/folder_handler.py:47 remark/core/folder_handler.py:90 +msgid "Failed to clear file attributes" +msgstr "" + +#: remark/core/folder_handler.py:52 +msgid "Failed to write desktop.ini" +msgstr "" + +#: remark/core/folder_handler.py:57 +msgid "Failed to set file attributes" +msgstr "" + +#: remark/core/folder_handler.py:62 +msgid "Failed to set folder attributes" +msgstr "" + +#: remark/core/folder_handler.py:66 +#, python-brace-format +msgid "Remark [{remark}] has been set for folder [{folder_path}]" +msgstr "" + +#: remark/core/folder_handler.py:70 +msgid "Remark added successfully, may take a few minutes to display" +msgstr "" + +#: remark/core/folder_handler.py:73 +#, python-brace-format +msgid "Failed to set remark: {error}" +msgstr "" + +#: remark/core/folder_handler.py:95 +msgid "Failed to remove remark" +msgstr "" + +#: remark/core/folder_handler.py:102 +msgid "Failed to restore file attributes" +msgstr "" + +#: remark/core/folder_handler.py:105 +msgid "Remark deleted successfully" +msgstr "" + +#: remark/storage/desktop_ini.py:313 +msgid "This file needs to be converted to UTF-16 encoding before modification." +msgstr "" + +#: remark/storage/desktop_ini.py:314 +msgid "" +"The original content will be preserved, only the encoding format will " +"change." +msgstr "" + +#: remark/storage/desktop_ini.py:321 +msgid "" +"\n" +"Current file content:" +msgstr "" + +#: remark/storage/desktop_ini.py:328 +msgid "" +"\n" +"Convert to UTF-16 encoding and continue? [Y/n]: " +msgstr "" + +#: remark/storage/desktop_ini.py:332 remark/storage/desktop_ini.py:347 +msgid "Operation cancelled." +msgstr "" + +#: remark/storage/desktop_ini.py:341 +msgid "Converted to UTF-16 encoding." +msgstr "" + +#: remark/storage/desktop_ini.py:346 +#, python-brace-format +msgid "Conversion failed: {error}" +msgstr "" + +#: remark/utils/platform.py:13 +msgid "" +"Error: This tool adds remarks to files/folders on Windows, other systems " +"are not supported." +msgstr "" + +#: remark/utils/platform.py:14 +#, python-brace-format +msgid "Current system: {system}" +msgstr "" + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f51189c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2513 @@ +{ + "name": "windows-folder-remark-docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "windows-folder-remark-docs", + "version": "1.0.0", + "devDependencies": { + "vitepress": "^1.0.0" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.13.0.tgz", + "integrity": "sha512-Zrqam12iorp3FjiKMXSTpedGYznZ3hTEOAr2oCxI8tbF8bS1kQHClyDYNq/eV0ewMNLyFkgZVWjaS+8spsOYiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.47.0.tgz", + "integrity": "sha512-aOpsdlgS9xTEvz47+nXmw8m0NtUiQbvGWNuSEb7fA46iPL5FxOmOUZkh8PREBJpZ0/H8fclSc7BMJCVr+Dn72w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.47.0.tgz", + "integrity": "sha512-EcF4w7IvIk1sowrO7Pdy4Ako7x/S8+nuCgdk6En+u5jsaNQM4rTT09zjBPA+WQphXkA2mLrsMwge96rf6i7Mow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.47.0.tgz", + "integrity": "sha512-Wzg5Me2FqgRDj0lFuPWFK05UOWccSMsIBL2YqmTmaOzxVlLZ+oUqvKbsUSOE5ud8Fo1JU7JyiLmEXBtgDKzTwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.47.0.tgz", + "integrity": "sha512-Ci+cn/FDIsDxSKMRBEiyKrqybblbk8xugo6ujDN1GSTv9RIZxwxqZYuHfdLnLEwLlX7GB8pqVyqrUSlRnR+sJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.47.0.tgz", + "integrity": "sha512-gsLnHPZmWcX0T3IigkDL2imCNtsQ7dR5xfnwiFsb+uTHCuYQt+IwSNjsd8tok6HLGLzZrliSaXtB5mfGBtYZvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.47.0.tgz", + "integrity": "sha512-PDOw0s8WSlR2fWFjPQldEpmm/gAoUgLigvC3k/jCSi/DzigdGX6RdC0Gh1RR1P8Cbk5KOWYDuL3TNzdYwkfDyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.47.0.tgz", + "integrity": "sha512-b5hlU69CuhnS2Rqgsz7uSW0t4VqrLMLTPbUpEl0QVz56rsSwr1Sugyogrjb493sWDA+XU1FU5m9eB8uH7MoI0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.47.0.tgz", + "integrity": "sha512-WvwwXp5+LqIGISK3zHRApLT1xkuEk320/EGeD7uYy+K8WwDd5OjXnhjuXRhYr1685KnkvWkq1rQ/ihCJjOfHpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.47.0.tgz", + "integrity": "sha512-j2EUFKAlzM0TE4GRfkDE3IDfkVeJdcbBANWzK16Tb3RHz87WuDfQ9oeEW6XiRE1/bEkq2xf4MvZesvSeQrZRDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.47.0.tgz", + "integrity": "sha512-+kTSE4aQ1ARj2feXyN+DMq0CIDHJwZw1kpxIunedkmpWUg8k3TzFwWsMCzJVkF2nu1UcFbl7xsIURz3Q3XwOXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.47.0.tgz", + "integrity": "sha512-Ja+zPoeSA2SDowPwCNRbm5Q2mzDvVV8oqxCQ4m6SNmbKmPlCfe30zPfrt9ho3kBHnsg37pGucwOedRIOIklCHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.47.0.tgz", + "integrity": "sha512-N6nOvLbaR4Ge+oVm7T4W/ea1PqcSbsHR4O58FJ31XtZjFPtOyxmnhgCmGCzP9hsJI6+x0yxJjkW5BMK/XI8OvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.47.0.tgz", + "integrity": "sha512-z1oyLq5/UVkohVXNDEY70mJbT/sv/t6HYtCvCwNrOri6pxBJDomP9R83KOlwcat+xqBQEdJHjbrPh36f1avmZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.68", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.68.tgz", + "integrity": "sha512-bQPl1zuZlX6AnofreA1v7J+hoPncrFMppqGboe/SH54jZO37meiBUGBqNOxEpc0HKfZGxJaVVJwZd4gdMYu3hw==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", + "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.27", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", + "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", + "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.27", + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", + "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", + "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", + "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", + "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/runtime-core": "3.5.27", + "@vue/shared": "3.5.27", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", + "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "vue": "3.5.27" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", + "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.47.0.tgz", + "integrity": "sha512-AGtz2U7zOV4DlsuYV84tLp2tBbA7RPtLA44jbVH4TTpDcc1dIWmULjHSsunlhscbzDydnjuFlNhflR3nV4VJaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.13.0", + "@algolia/client-abtesting": "5.47.0", + "@algolia/client-analytics": "5.47.0", + "@algolia/client-common": "5.47.0", + "@algolia/client-insights": "5.47.0", + "@algolia/client-personalization": "5.47.0", + "@algolia/client-query-suggestions": "5.47.0", + "@algolia/client-search": "5.47.0", + "@algolia/ingestion": "1.47.0", + "@algolia/monitoring": "1.47.0", + "@algolia/recommend": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.28.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", + "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", + "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-sfc": "3.5.27", + "@vue/runtime-dom": "3.5.27", + "@vue/server-renderer": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5b1dc3e --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "windows-folder-remark-docs", + "version": "1.0.0", + "type": "module", + "description": "Documentation for Windows Folder Remark Tool", + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "devDependencies": { + "vitepress": "^1.0.0" + } +} diff --git a/pyproject.toml b/pyproject.toml index b254f31..7c2aa5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "windows-folder-remark" -version = "2.0.5" +version = "2.0.6" description = "Windows 文件夹备注工具" readme = "README.md" requires-python = ">=3.11" @@ -11,7 +11,7 @@ license = {text = "MIT"} authors = [ {name = "Piratf"} ] -keywords = ["windows", "folder", "remark", "desktop.ini"] +keywords = ["windows", "folder", "remark", "comment", "desktop.ini"] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", @@ -37,6 +37,8 @@ dev = [ "pyfakefs>=5.0.0", "pre-commit>=3.0.0", "pyinstaller>=6.0.0", + "babel>=2.16.0", + "polint>=0.3.0", ] # 命令行入口 @@ -170,3 +172,15 @@ exclude_lines = [ "if TYPE_CHECKING:", "@abstractmethod", ] + + +# Babel - 国际化配置 +# https://babel.pocoo.org/ +[tool.babel] +# 目录配置 +locale_dir = "locale" +domain = "messages" +# 源文件编码 +input_encoding = "utf-8" +# 输出文件编码 +output_encoding = "utf-8" diff --git a/remark.spec b/remark.spec index 8bff250..9c946a7 100644 --- a/remark.spec +++ b/remark.spec @@ -82,11 +82,20 @@ hiddenimports = collect_submodules('remark') + [ 'packaging.version', ] +# Collect locale translation files (.mo) +# PyInstaller 会将这些文件复制到 exe 的临时目录 +locale_datas = [] +for lang in ['zh', 'en']: + lang_dir = os.path.join('locale', lang, 'LC_MESSAGES') + mo_file = os.path.join(lang_dir, 'messages.mo') + if os.path.exists(mo_file): + locale_datas.append((mo_file, lang_dir)) + a = Analysis( [os.path.join("remark", "cli", "commands.py")], pathex=[], binaries=[], - datas=[], + datas=locale_datas, hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, diff --git a/remark/cli/__main__.py b/remark/cli/__main__.py new file mode 100644 index 0000000..7803fcd --- /dev/null +++ b/remark/cli/__main__.py @@ -0,0 +1,8 @@ +""" +Main entry point for running remark.cli as a module. +""" + +from remark.cli.commands import main + +if __name__ == "__main__": + main() diff --git a/remark/cli/commands.py b/remark/cli/commands.py index 9a6356f..759c6fb 100644 --- a/remark/cli/commands.py +++ b/remark/cli/commands.py @@ -11,6 +11,7 @@ from remark.core.folder_handler import FolderCommentHandler from remark.gui import remark_dialog +from remark.i18n import _ as _, set_language from remark.utils import registry from remark.utils.path_resolver import find_candidates from remark.utils.platform import check_platform @@ -48,13 +49,13 @@ def __init__(self): else: self._update_check_done.set() # 不需要检查,直接标记完成 - def _validate_folder(self, path): + def _validate_folder(self, path: str) -> bool: """验证路径是否为文件夹""" if not os.path.exists(path): - print("路径不存在:", path) + print(_("Path does not exist: {path}").format(path=path)) return False if not self.handler.supports(path): - print("路径不是文件夹:", path) + print(_("Path is not a folder: {path}").format(path=path)) return False return True @@ -76,21 +77,21 @@ def check_update_now(self) -> bool: Returns: True 如果有新版本,False 否则 """ - print(f"当前版本: {get_version()}") - print("正在检查更新...") + print(_("Current version: {version}").format(version=get_version())) + print(_("Checking for updates...")) update = check_updates_manual(get_version()) if update: - print(f"\n发现新版本: {update['tag_name']}") - print(f"更新说明: {update['body'][:300]}...") - print(f"完整更新日志: {update['html_url']}") - response = input("\n是否立即更新? [Y/n]: ").lower() + print(_("\nNew version found: {tag_name}").format(tag_name=update["tag_name"])) + print(_("Update notes: {notes}").format(notes=update["body"][:300])) + print(_("Full changelog: {url}").format(url=update["html_url"])) + response = input(_("\nUpdate now? [Y/n]: ")).lower() if response in ("", "y", "yes"): self._perform_update(update) return True else: - print("已是最新版本") + print(_("Already at the latest version")) return False def _wait_for_update_check(self, timeout: float = 2.0) -> None: @@ -106,51 +107,55 @@ def _prompt_update(self) -> None: update = self.pending_update if update is None: return - print(f"\n发现新版本: {update['tag_name']} (当前版本: {get_version()})") - print(f"更新说明: {update['body'][:200]}...") - print(f"完整更新日志: {update['html_url']}") - response = input("是否立即更新? [Y/n]: ").lower() + print( + _("\nNew version available: {tag_name} (Current version: {version})").format( + tag_name=update["tag_name"], version=get_version() + ) + ) + print(_("Update notes: {notes}").format(notes=update["body"][:200])) + print(_("Full changelog: {url}").format(url=update["html_url"])) + response = input(_("Update now? [Y/n]: ")).lower() if response in ("", "y", "yes"): self._perform_update(update) def _perform_update(self, update: dict) -> None: """执行更新流程""" try: - print("正在下载新版本...") + print(_("Downloading new version...")) # 下载到临时目录 new_exe = os.path.join( tempfile.gettempdir(), f"windows-folder-remark-{update['tag_name']}.exe" ) download_update(update["download_url"], new_exe) - print("下载完成,准备更新...") + print(_("Download complete, preparing update...")) old_exe = get_executable_path() script_path = create_update_script(old_exe, new_exe) - print("更新程序已启动,程序即将退出...") - print("请等待几秒钟,更新将自动完成。") + print(_("Update program has started, the application will exit...")) + print(_("Please wait a few moments, the update will complete automatically.")) trigger_update(script_path) sys.exit(0) except urllib.error.URLError as e: err_msg = str(e) if "closed connection" in err_msg.lower() or "connection reset" in err_msg.lower(): - print("下载失败:连接被服务器断开") - print("请稍后重试,或访问以下链接手动下载:") + print(_("Download failed: Connection reset by server")) + print(_("Please try again later, or visit the following link to download manually:")) print(f" {update['html_url']}") elif "timeout" in err_msg.lower(): - print("下载失败:请求超时") - print("请检查网络连接,或访问以下链接手动下载:") + print(_("Download failed: Request timeout")) + print(_("Please check your network connection, or visit the following link to download manually:")) print(f" {update['html_url']}") elif "no route to host" in err_msg.lower() or "hostname" in err_msg.lower(): - print("下载失败:无法连接到服务器") - print("请检查网络连接,或访问以下链接手动下载:") + print(_("Download failed: Unable to connect to server")) + print(_("Please check your network connection, or visit the following link to download manually:")) print(f" {update['html_url']}") else: - print("下载失败,请检查网络连接或手动下载更新") + print(_("Download failed, please check your network or download manually")) print(f" {update['html_url']}") except Exception as e: - print(f"更新失败: {e}") - print(f"手动下载: {update['html_url']}") + print(_("Update failed: {error}").format(error=e)) + print(_("Manual download: {url}").format(url=update["html_url"])) def add_comment(self, path, comment): """添加备注""" @@ -167,23 +172,23 @@ def delete_comment(self, path): def install_menu(self) -> bool: """安装右键菜单""" if registry.install_context_menu(): - print("右键菜单安装成功") + print(_("Right-click menu installed successfully")) print("") - print("使用说明:") - print(" Windows 10: 右键文件夹可直接看到「添加文件夹备注」") - print(" Windows 11: 右键文件夹 → 点击「显示更多选项」→ 添加文件夹备注") + print(_("Usage Instructions:")) + print(_(" Windows 10: Right-click folder to see 'Add Folder Remark'")) + print(_(" Windows 11: Right-click folder → Click 'Show more options' → Add Folder Remark")) return True else: - print("右键菜单安装失败") + print(_("Right-click menu installation failed")) return False def uninstall_menu(self) -> bool: """卸载右键菜单""" if registry.uninstall_context_menu(): - print("右键菜单已卸载") + print(_("Right-click menu uninstalled")) return True else: - print("右键菜单卸载失败") + print(_("Right-click menu uninstallation failed")) return False def gui_mode(self, folder_path: str) -> bool: @@ -198,7 +203,7 @@ def gui_mode(self, folder_path: str) -> bool: return result is not False return False - def view_comment(self, path): + def view_comment(self, path: str) -> None: """查看备注""" if self._validate_folder(path): # 检查 desktop.ini 编码 @@ -209,85 +214,87 @@ def view_comment(self, path): detected_encoding, is_utf16 = DesktopIniHandler.detect_encoding(desktop_ini_path) if not is_utf16: print( - f"警告: desktop.ini 文件编码为 {detected_encoding or '未知'},不是标准的 UTF-16。" + _( + "Warning: desktop.ini file encoding is {encoding}, not standard UTF-16." + ).format(encoding=detected_encoding or _("unknown")) ) - print("这可能导致中文等特殊字符显示异常。") + print(_("This may cause Chinese and other special characters to display abnormally.")) # 询问是否修复 while True: - response = input("是否修复编码为 UTF-16?[Y/n]: ").strip().lower() + response = input(_("Fix encoding to UTF-16? [Y/n]: ")).strip().lower() if response in ("", "y", "yes"): if DesktopIniHandler.fix_encoding(desktop_ini_path, detected_encoding): - print("✓ 已修复为 UTF-16 编码") + print(_("Fixed to UTF-16 encoding")) else: - print("✗ 修复失败") + print(_("Failed to fix encoding")) break elif response in ("n", "no"): - print("跳过编码修复") + print(_("Skip encoding fix")) break else: - print("请输入 Y 或 n") + print(_("Please enter Y or n")) print() # 空行分隔 comment = self.handler.get_comment(path) if comment: - print("当前备注:", comment) + print(_("Current remark: {remark}").format(remark=comment)) else: - print("该文件夹没有备注") + print(_("This folder has no remark")) - def interactive_mode(self): + def interactive_mode(self) -> None: """交互模式""" version = get_version() - print("Windows 文件夹备注工具 v" + version) - print("提示: 按 Ctrl + C 退出程序" + os.linesep) + print(_("Windows Folder Remark Tool v{version}").format(version=version)) + print(_("Tip: Press Ctrl + C to exit") + os.linesep) - input_path_msg = "请输入文件夹路径(或拖动到这里): " - input_comment_msg = "请输入备注:" + input_path_msg = _("Enter folder path (or drag here): ") + input_comment_msg = _("Enter remark:") while True: try: path = input(input_path_msg).replace('"', "").strip() if not os.path.exists(path): - print("路径不存在,请重新输入") + print(_("Path does not exist, please re-enter")) continue if not os.path.isdir(path): - print('这是一个"文件",当前仅支持为"文件夹"添加备注') + print(_("This is a 'file', currently only supports adding remarks to 'folders'")) continue comment = input(input_comment_msg) while not comment: - print("备注不要为空哦") + print(_("Remark cannot be empty")) comment = input(input_comment_msg) self.add_comment(path, comment) except KeyboardInterrupt: - print(" ❤ 感谢使用") + print(_(" ❤ Thank you for using")) break - print(os.linesep + "继续处理或按 Ctrl + C 退出程序" + os.linesep) + print(os.linesep + _("Continue processing or press Ctrl + C to exit") + os.linesep) - def show_help(self): + def show_help(self) -> None: """显示帮助信息""" - print("Windows 文件夹备注工具") - print("使用方法:") - print(" 交互模式: python remark.py") - print(" 命令行模式: python remark.py [选项] [参数]") - print("选项:") - print(" --install 安装右键菜单") - print(" --uninstall 卸载右键菜单") - print(" --update 检查更新") - print(" --gui <路径> GUI 模式(右键菜单调用)") - print(" --delete <路径> 删除备注") - print(" --view <路径> 查看备注") - print(" --help, -h 显示帮助信息") - print("示例:") - print(' [添加备注] python remark.py "C:\\\\MyFolder" "这是我的文件夹"') - print(' [删除备注] python remark.py --delete "C:\\\\MyFolder"') - print(' [查看当前备注] python remark.py --view "C:\\\\MyFolder"') - print(" [安装右键菜单] python remark.py --install") - print(" [检查更新] python remark.py --update") + print(_("Windows Folder Remark Tool")) + print(_("Usage:")) + print(_(" Interactive mode: python remark.py")) + print(_(" Command line mode: python remark.py [options] [arguments]")) + print(_("Options:")) + print(_(" --install Install right-click menu")) + print(_(" --uninstall Uninstall right-click menu")) + print(_(" --update Check for updates")) + print(_(" --gui GUI mode (called from right-click menu)")) + print(_(" --delete Delete remark")) + print(_(" --view View remark")) + print(_(" --help, -h Show help information")) + print(_("Examples:")) + print(_(' [Add remark] python remark.py "C:\\\\MyFolder" "My Folder"')) + print(_(' [Delete remark] python remark.py --delete "C:\\\\MyFolder"')) + print(_(' [View current remark] python remark.py --view "C:\\\\MyFolder"')) + print(_(" [Install right-click menu] python remark.py --install")) + print(_(" [Check for updates] python remark.py --update")) def _select_from_multiple_candidates( self, candidates: list, show_remaining: bool = False @@ -344,26 +351,26 @@ def _handle_ambiguous_path(self, args_list: list[str]) -> tuple[str | None, str candidates = find_candidates(args_list) if not candidates: - print("错误: 路径不存在或未使用引号") - print("提示: 路径包含空格时请使用引号") - print(' windows-folder-remark "C:\\\\My Documents" "备注内容"') + print(_("Error: Path does not exist or not quoted")) + print(_("Hint: Use quotes when path contains spaces")) + print(_(' windows-folder-remark "C:\\\\My Documents" "Remark content"')) return None, None if len(candidates) == 1: path, remaining, path_type = candidates[0] - print(f"检测到路径: {path}") + print(_("Detected path: {path}").format(path=path)) if path_type == "file": - print("错误: 这是一个文件,工具只能为文件夹设置备注") + print(_("Error: This is a file, the tool can only set remarks for folders")) return None, None if remaining: comment = " ".join(remaining) - print(f"备注内容: {comment}") + print(_("Remark content: {remark}").format(remark=comment)) else: - print("(将查看现有备注)") + print(_("(Will view existing remark)")) - if input("是否继续? [Y/n]: ").lower() in ("", "y", "yes"): + if input(_("Continue? [Y/n]: ")).lower() in ("", "y", "yes"): return str(path), " ".join(remaining) if remaining else None return None, None @@ -396,7 +403,7 @@ def _resolve_path_from_ambiguous_args(self, args_list: list[str]) -> str | None: if path_type == "folder": return str(path) else: - print("错误: 这是一个文件,工具只能为文件夹设置备注") + print(_("Error: This is a file, the tool can only set remarks for folders")) return None result = self._select_from_multiple_candidates(candidates, show_remaining=False) @@ -404,7 +411,7 @@ def _resolve_path_from_ambiguous_args(self, args_list: list[str]) -> str | None: return result[0] return None - def run(self, argv=None): + def run(self, argv=None) -> None: """运行 CLI""" if not check_platform(): sys.exit(1) @@ -418,9 +425,14 @@ def run(self, argv=None): parser.add_argument("--delete", metavar="PATH", help="删除备注") parser.add_argument("--view", metavar="PATH", help="查看备注") parser.add_argument("--help", "-h", action="store_true", help="显示帮助信息") + parser.add_argument("--lang", "-L", metavar="LANG", help="设置语言 (en, zh)", dest="lang") args = parser.parse_args(argv) + # 设置语言 + if args.lang: + set_language(args.lang) + if args.help: self.show_help() elif args.install: @@ -464,16 +476,23 @@ def run(self, argv=None): self.interactive_mode() -def main(): +def main() -> None: """主入口""" + # 强制设置控制台编码为 UTF-8,支持中文等特殊字符输出 + # 这对于 Windows 系统特别重要,因为默认控制台编码可能是 GBK + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + cli = CLI() try: cli.run() except KeyboardInterrupt: - print("\n操作已取消") + print(_("\nOperation cancelled")) sys.exit(0) except Exception as e: - print("发生错误:", str(e)) + print(_("An error occurred: {error}").format(error=str(e))) sys.exit(1) finally: # 等待后台检测完成(最多等待 2 秒) diff --git a/remark/core/folder_handler.py b/remark/core/folder_handler.py index b1b5459..286ad04 100644 --- a/remark/core/folder_handler.py +++ b/remark/core/folder_handler.py @@ -10,6 +10,7 @@ import os from remark.core.base import CommentHandler +from remark.i18n import _ as _ from remark.storage.desktop_ini import DesktopIniHandler from remark.utils.constants import MAX_COMMENT_LENGTH @@ -17,20 +18,24 @@ class FolderCommentHandler(CommentHandler): """文件夹备注处理器""" - def set_comment(self, folder_path, comment): + def set_comment(self, folder_path: str, comment: str) -> bool: """设置文件夹备注""" if not os.path.isdir(folder_path): - print("路径不是文件夹:", folder_path) + print(_("Path is not a folder: {folder_path}").format(folder_path=folder_path)) return False if len(comment) > MAX_COMMENT_LENGTH: - print("备注长度超过限制,最大长度为", MAX_COMMENT_LENGTH, "个字符") + print( + _("Remark length exceeds limit, maximum length is {length} characters").format( + length=MAX_COMMENT_LENGTH + ) + ) comment = comment[:MAX_COMMENT_LENGTH] return self._set_comment_desktop_ini(folder_path, comment) @staticmethod - def _set_comment_desktop_ini(folder_path, comment): + def _set_comment_desktop_ini(folder_path: str, comment: str) -> bool: """使用 desktop.ini 设置备注""" desktop_ini_path = DesktopIniHandler.get_path(folder_path) @@ -39,63 +44,67 @@ def _set_comment_desktop_ini(folder_path, comment): if DesktopIniHandler.exists( folder_path ) and not DesktopIniHandler.clear_file_attributes(desktop_ini_path): - print("清除文件属性失败") + print(_("Failed to clear file attributes")) return False # 使用 UTF-16 编码写入 desktop.ini if not DesktopIniHandler.write_info_tip(folder_path, comment): - print("写入 desktop.ini 失败") + print(_("Failed to write desktop.ini")) return False # 设置 desktop.ini 文件为隐藏和系统属性 if not DesktopIniHandler.set_file_hidden_system_attributes(desktop_ini_path): - print("设置文件属性失败") + print(_("Failed to set file attributes")) return False # 设置文件夹为只读属性(使 desktop.ini 生效) if not DesktopIniHandler.set_folder_system_attributes(folder_path): - print("设置文件夹属性失败") + print(_("Failed to set folder attributes")) return False - print(f"已经为文件夹 [{folder_path}] 设置备注 [{comment}]") - print("备注添加成功,可能需要过几分钟才会显示") + print( + _("Remark [{remark}] has been set for folder [{folder_path}]").format( + remark=comment, folder_path=folder_path + ) + ) + print(_("Remark added successfully, may take a few minutes to display")) return True except Exception as e: - print("设置备注失败:", str(e)) + print(_("Failed to set remark: {error}").format(error=str(e))) return False - def get_comment(self, folder_path): + def get_comment(self, folder_path: str) -> str | None: """获取文件夹备注""" return DesktopIniHandler.read_info_tip(folder_path) - def delete_comment(self, folder_path): + def delete_comment(self, folder_path: str) -> bool: """删除文件夹备注""" desktop_ini_path = DesktopIniHandler.get_path(folder_path) if not DesktopIniHandler.exists(folder_path): - print("该文件夹没有备注") + print(_("This folder has no remark")) return True # 清除文件属性以便修改 if not DesktopIniHandler.clear_file_attributes(desktop_ini_path): - print("清除文件属性失败") + print(_("Failed to clear file attributes")) return False # 移除 InfoTip 行(保留其他设置如 IconResource) if not DesktopIniHandler.remove_info_tip(folder_path): - print("移除备注失败") + print(_("Failed to remove remark")) return False # 如果 desktop.ini 仍存在,恢复文件属性 if DesktopIniHandler.exists( folder_path ) and not DesktopIniHandler.set_file_hidden_system_attributes(desktop_ini_path): - print("恢复文件属性失败") + print(_("Failed to restore file attributes")) return False - print("备注删除成功") + print(_("Remark deleted successfully")) return True - def supports(self, path): + def supports(self, path: str) -> bool: """检查是否支持该路径""" return os.path.isdir(path) diff --git a/remark/i18n.py b/remark/i18n.py new file mode 100644 index 0000000..bbe64bb --- /dev/null +++ b/remark/i18n.py @@ -0,0 +1,225 @@ +""" +Internationalization (i18n) module for Windows Folder Remark tool. + +This module provides translation support using gettext. +""" +from __future__ import annotations + +import ctypes +import gettext +import locale +import os +import platform +import sys +from pathlib import Path +from typing import Final + +# 翻译域 +DOMAIN: Final = "messages" + +# 支持的语言列表 +SUPPORTED_LANGUAGES: Final = ("en", "zh") + + +def _get_locale_dir() -> Path: + """ + 获取翻译文件目录路径. + + 支持 PyInstaller 打包环境: + - 打包后:sys._MEIPASS/locale(文件解压到临时目录的 locale 子目录) + - 开发环境:使用项目根目录下的 locale 目录 + + Returns: + locale 目录的路径 + """ + # PyInstaller 打包后的临时目录,文件被解压到 _MEIPASS/locale/ + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + return Path(sys._MEIPASS) / "locale" + + # 开发环境:使用项目根目录 + return Path(__file__).parent.parent / "locale" + + +# 翻译文件目录(运行时计算) +LOCALE_DIR: Final = _get_locale_dir() + + +def _get_windows_locale() -> str | None: + """ + 在 Windows 上使用 Windows API 获取用户默认区域设置名称. + + Returns: + 区域设置名称(如 'zh-CN', 'en-US'),如果获取失败则返回 None + """ + try: + # GetUserDefaultLocaleName 返回 locale 名称(如 'zh-CN', 'en-US') + # 缓冲区大小为 LOCALE_NAME_MAX_LENGTH (85) + buffer_size: int = 85 + buffer: ctypes.Array[ctypes.c_wchar] = ctypes.create_unicode_buffer(buffer_size) + + # kernel32.dll 中的 GetUserDefaultLocaleName 函数 + # 原型: int GetUserDefaultLocaleName(LPWSTR lpLocaleName, int cchLocaleName) + kernel32 = ctypes.windll.kernel32 + kernel32.GetUserDefaultLocaleName.restype = ctypes.c_int + kernel32.GetUserDefaultLocaleName.argtypes = [ + ctypes.POINTER(ctypes.c_wchar), + ctypes.c_int, + ] + + result: int = kernel32.GetUserDefaultLocaleName(buffer, buffer_size) + + if result > 0: + return buffer.value.strip() + except (AttributeError, OSError, ValueError): + pass + + return None + + +def get_system_language() -> str: + """ + 获取系统语言设置. + + 优先级顺序: + 1. 环境变量 LANG(最容易被测试控制) + 2. Windows 平台的 Windows API + 3. locale.getlocale() + 4. 默认返回英文 + + Returns: + 语言代码(如 'en', 'zh'),如果不支持则返回默认的 'en' + """ + # 优先从环境变量获取(便于测试控制) + lang = os.environ.get("LANG", "") + if lang: + # 提取语言代码(如 zh_CN.UTF-8 -> zh) + lang_code = lang.split(".")[0].split("_")[0] + if lang_code in SUPPORTED_LANGUAGES: + return lang_code + # 处理完整语言代码(如 zh_CN -> zh) + if "_" in lang: + full_lang = lang.split(".")[0] + if full_lang in SUPPORTED_LANGUAGES: + return full_lang + + # Windows 平台使用 Windows API + if platform.system() == "Windows": + windows_locale = _get_windows_locale() + if windows_locale: + # Windows locale 格式为 'zh-CN', 'en-US' 等 + # 提取语言部分(zh-CN -> zh) + lang_code = windows_locale.split("-")[0] + if lang_code in SUPPORTED_LANGUAGES: + return lang_code + + # 尝试从 locale 获取 + try: + loc = locale.getlocale()[0] + if loc: + # locale 格式可能是 'zh_CN', 'zh-CN', 'Chinese_China' 等 + normalized = loc.replace("-", "_") + if normalized in SUPPORTED_LANGUAGES: + return normalized + # 尝试只取语言部分 + lang_code = normalized.split("_")[0] + if lang_code in SUPPORTED_LANGUAGES: + return lang_code + # 处理 Windows 特殊格式(如 'Chinese_China' -> 'zh') + if normalized.startswith("Chinese"): + return "zh" + except (ValueError, AttributeError): + pass + + # 默认返回英文 + return "en" + + +def init_translation(language: str | None = None) -> gettext.GNUTranslations: + """ + 初始化翻译. + + Args: + language: 语言代码,如果为 None 则使用系统语言 + + Returns: + 翻译函数 + """ + if language is None: + language = get_system_language() + + # 确保语言受支持 + if language not in SUPPORTED_LANGUAGES: + language = "en" + + # 尝试加载翻译 + try: + translator = gettext.translation( + domain=DOMAIN, + localedir=str(LOCALE_DIR), + languages=[language], + fallback=True, + ) + return translator + except Exception: + # 如果加载失败,使用空翻译(返回原字符串) + return gettext.NullTranslations() + + +# 全局翻译函数 +_translator: gettext.GNUTranslations | gettext.NullTranslations | None = None + + +def get_translator() -> gettext.GNUTranslations | gettext.NullTranslations: + """ + 获取当前翻译器. + + Returns: + 翻译器实例 + """ + global _translator + if _translator is None: + _translator = init_translation() + return _translator + + +def set_language(language: str) -> None: + """ + 设置当前语言. + + Args: + language: 语言代码(如 'en', 'zh_CN') + """ + global _translator + _translator = init_translation(language) + + +def gettext_function(message: str) -> str: + """ + 翻译函数(用于在代码中标记可翻译字符串). + + Args: + message: 要翻译的字符串 + + Returns: + 翻译后的字符串 + """ + return get_translator().gettext(message) + + +def ngettext_function(singular: str, plural: str, n: int) -> str: + """ + 复数形式翻译函数. + + Args: + singular: 单数形式 + plural: 复数形式 + n: 数量 + + Returns: + 翻译后的字符串 + """ + return get_translator().ngettext(singular, plural, n) + + +# 默认导出的翻译函数 +_ = gettext_function diff --git a/remark/storage/desktop_ini.py b/remark/storage/desktop_ini.py index f62effe..51c9f7e 100644 --- a/remark/storage/desktop_ini.py +++ b/remark/storage/desktop_ini.py @@ -14,6 +14,8 @@ import codecs import os +from remark.i18n import _ as _ + class EncodingConversionCanceled(Exception): # noqa: N818 """编码转换被用户取消""" @@ -303,42 +305,46 @@ def ensure_utf16_encoding(file_path): return # 已经是 UTF-16 # 文件不是 UTF-16,需要用户确认 - print(f"警告:desktop.ini 文件编码为 {encoding or '未知'},不是标准的 UTF-16。") - print("修改此文件前需要先转换为 UTF-16 编码。") - print("原内容会被保留,仅改变编码格式。") + print( + _("Warning: desktop.ini file encoding is {encoding}, not standard UTF-16.").format( + encoding=encoding or _("unknown") + ) + ) + print(_("This file needs to be converted to UTF-16 encoding before modification.")) + print(_("The original content will be preserved, only the encoding format will change.")) try: # 显示文件预览 with codecs.open(file_path, "r", encoding=encoding or "utf-8") as f: content = f.read() - print("\n当前文件内容:") + print(_("\nCurrent file content:")) print("-" * 40) print(content) print("-" * 40) # 用户确认 while True: - response = input("\n是否转换为 UTF-16 编码后继续?[Y/n]: ").strip().lower() + response = input(_("\nConvert to UTF-16 encoding and continue? [Y/n]: ")).strip().lower() if response in ("", "y", "yes"): break elif response in ("n", "no"): - print("操作已取消。") + print(_("Operation cancelled.")) raise EncodingConversionCanceled("用户拒绝编码转换") else: - print("请输入 Y 或 n") + print(_("Please enter Y or n")) # 执行转换 with codecs.open(file_path, "w", encoding=DESKTOP_INI_ENCODING) as f: f.write(content) - print("✓ 已转换为 UTF-16 编码。") + print(_("Converted to UTF-16 encoding.")) except EncodingConversionCanceled: raise except Exception as e: - print(f"转换失败: {e}") - print("操作已取消。") + print(_("Conversion failed: {error}").format(error=e)) + print(_("Operation cancelled.")) raise EncodingConversionCanceled(f"编码转换失败: {e}") from e @staticmethod diff --git a/remark/utils/platform.py b/remark/utils/platform.py index 7141acc..2714a39 100644 --- a/remark/utils/platform.py +++ b/remark/utils/platform.py @@ -4,11 +4,13 @@ import platform +from remark.i18n import _ as _ -def check_platform(): + +def check_platform() -> bool: """检查是否为 Windows 系统""" if platform.system() != "Windows": - print("错误: 此工具为 Windows 系统中的文件/文件夹添加备注,暂不支持其他系统。") - print("当前系统:", platform.system()) + print(_("Error: This tool adds remarks to files/folders on Windows, other systems are not supported.")) + print(_("Current system: {system}").format(system=platform.system())) return False return True diff --git a/tests/conftest.py b/tests/conftest.py index 4510db3..d43e52b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ """共享测试配置""" +import os import sys + from pathlib import Path import pytest @@ -8,9 +10,17 @@ # 项目根目录添加到路径 sys.path.insert(0, str(Path(__file__).parent.parent)) +# 在导入任何模块之前设置语言环境变量,确保翻译使用中文 +os.environ["LANG"] = "zh" + +# 立即设置翻译器为中文,确保在导入任何模块之前生效 +from remark.i18n import set_language +set_language("zh") + def pytest_configure(config): """pytest 配置钩子 - 定义 markers""" + import sys config.addinivalue_line("markers", "unit: 单元测试(使用 mock)") config.addinivalue_line("markers", "integration: 集成测试(真实文件系统)") config.addinivalue_line("markers", "windows: 仅在 Windows 上运行") @@ -24,6 +34,10 @@ def pytest_configure(config): if sys.stderr.encoding.lower() not in ("utf-8", "utf-16", "utf-16-le", "utf-16-be"): sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace") + # 强制重新初始化翻译器为中文 + from remark.i18n import set_language + set_language("zh") + def pytest_collection_modifyitems(config, items): """自动跳过非 Windows 平台上的 Windows 测试""" @@ -32,3 +46,11 @@ def pytest_collection_modifyitems(config, items): for item in items: if "windows" in item.keywords: item.add_marker(skip_windows) + + +@pytest.fixture(autouse=True, scope="session") +def set_chinese_language(): + """在整个测试会话开始时设置语言为中文""" + from remark.i18n import set_language + set_language("zh") + yield diff --git a/tests/unit/test_i18n.py b/tests/unit/test_i18n.py new file mode 100644 index 0000000..0337c80 --- /dev/null +++ b/tests/unit/test_i18n.py @@ -0,0 +1,207 @@ +"""国际化 (i18n) 单元测试""" + +from unittest.mock import MagicMock, patch + +import pytest + +from remark.i18n import ( + SUPPORTED_LANGUAGES, + _get_windows_locale, + get_system_language, + gettext_function, + init_translation, + ngettext_function, + set_language, +) + + +@pytest.mark.unit +class TestGetWindowsLocale: + """Windows locale 获取测试""" + + @pytest.mark.parametrize( + "locale_name,expected", + [ + ("zh-CN", "zh-CN"), + ("en-US", "en-US"), + ("zh-TW", "zh-TW"), + ], + ) + def test_get_windows_locale_success(self, locale_name, expected): + """测试成功获取 Windows locale""" + mock_buffer = MagicMock() + mock_buffer.value = locale_name + mock_buffer.strip.return_value = locale_name + + with patch("ctypes.create_unicode_buffer", return_value=mock_buffer): + with patch("ctypes.windll.kernel32.GetUserDefaultLocaleName", return_value=len(locale_name)): + result = _get_windows_locale() + assert result == expected + + def test_get_windows_locale_api_fails(self): + """测试 Windows API 调用失败""" + with patch("ctypes.windll.kernel32.GetUserDefaultLocaleName", return_value=0): + result = _get_windows_locale() + assert result is None + + def test_get_windows_locale_exception(self): + """测试 Windows API 抛出异常""" + with patch("ctypes.windll.kernel32.GetUserDefaultLocaleName", side_effect=OSError): + result = _get_windows_locale() + assert result is None + + +@pytest.mark.unit +class TestGetSystemLanguage: + """系统语言获取测试""" + + @pytest.mark.parametrize( + "windows_locale,expected", + [ + ("zh-CN", "zh"), + ("en-US", "en"), + ("zh-TW", "zh"), + ], + ) + def test_windows_platform_priority(self, windows_locale, expected): + """测试 Windows 平台优先使用 Windows API(当 LANG 未设置时)""" + with patch("remark.i18n.platform.system", return_value="Windows"): + with patch("remark.i18n._get_windows_locale", return_value=windows_locale): + with patch.dict("os.environ", {}, clear=True): + result = get_system_language() + assert result in SUPPORTED_LANGUAGES + assert result == expected + + @pytest.mark.parametrize( + "windows_locale", + ["ja-JP", "ko-KR", "fr-FR"], + ) + def test_windows_unsupported_locale_fallback(self, windows_locale): + """测试不支持的语言回退到默认""" + with patch("remark.i18n.platform.system", return_value="Windows"): + with patch("remark.i18n._get_windows_locale", return_value=windows_locale): + with patch.dict("os.environ", {}, clear=True): + with patch("remark.i18n.locale.getlocale", return_value=(None, None)): + result = get_system_language() + assert result == "en" + + def test_windows_api_null_fallback_to_locale(self): + """测试 Windows API 返回 None 时回退到 locale.getlocale()""" + with patch("remark.i18n.platform.system", return_value="Windows"): + with patch("remark.i18n._get_windows_locale", return_value=None): + with patch.dict("os.environ", {}, clear=True): + with patch("remark.i18n.locale.getlocale", return_value=("zh_CN", "cp1252")): + result = get_system_language() + assert result == "zh" + + @pytest.mark.parametrize( + "locale_value,expected", + [ + ("zh_CN", "zh"), + ("zh-CN", "zh"), + ("Chinese_China", "zh"), + ("en_US", "en"), + ("en-GB", "en"), + ("English_United States", "en"), + ], + ) + def test_locale_getlocale_variations(self, locale_value, expected): + """测试 locale.getlocale() 的各种返回值格式""" + with patch("remark.i18n.platform.system", return_value="Linux"): + with patch("remark.i18n._get_windows_locale", return_value=None): + with patch.dict("os.environ", {}, clear=True): + with patch("remark.i18n.locale.getlocale", return_value=(locale_value, "cp1252")): + result = get_system_language() + assert result == expected + + @pytest.mark.parametrize( + "env_lang,expected", + [ + ("zh_CN.UTF-8", "zh"), + ("en_US.UTF-8", "en"), + ("zh.UTF-8", "zh"), + ("en", "en"), + ], + ) + def test_lang_environment_variable(self, env_lang, expected): + """测试 LANG 环境变量""" + with patch("remark.i18n.platform.system", return_value="Linux"): + with patch("remark.i18n._get_windows_locale", return_value=None): + with patch.dict("os.environ", {"LANG": env_lang}): + with patch("remark.i18n.locale.getlocale", return_value=(None, None)): + result = get_system_language() + assert result == expected + + def test_all_methods_fallback_to_default(self): + """测试所有方法都失败时回退到默认语言""" + with patch("remark.i18n.platform.system", return_value="Linux"): + with patch("remark.i18n._get_windows_locale", return_value=None): + with patch.dict("os.environ", {}, clear=True): + with patch("remark.i18n.locale.getlocale", return_value=(None, None)): + result = get_system_language() + assert result == "en" + + +@pytest.mark.unit +class TestInitTranslation: + """翻译初始化测试""" + + @pytest.mark.parametrize( + "language,expected_domain", + [ + ("en", "messages"), + ("zh", "messages"), + ], + ) + def test_init_translation_supported_language(self, language, expected_domain): + """测试支持的语言初始化""" + translator = init_translation(language) + assert translator is not None + assert translator.gettext("test") == "test" + + def test_init_translation_unsupported_language_fallback(self): + """测试不支持的语言回退到英文""" + translator = init_translation("fr") + assert translator is not None + # 应该回退到 NullTranslations 或英文翻译 + assert translator.gettext("test") == "test" + + def test_init_translation_none_uses_system(self): + """测试 None 作为参数使用系统语言""" + with patch("remark.i18n.get_system_language", return_value="zh"): + translator = init_translation(None) + assert translator is not None + + +@pytest.mark.unit +class TestSetLanguage: + """设置语言测试""" + + def test_set_language_updates_translator(self): + """测试设置语言更新翻译器""" + set_language("zh") + # 验证语言已被设置(通过调用 gettext_function) + result = gettext_function("Windows Folder Remark Tool") + # 如果成功加载中文翻译,应该返回中文字符串 + # 否则返回原字符串 + assert isinstance(result, str) + + +@pytest.mark.unit +class TestGetTextFunction: + """翻译函数测试""" + + def test_gettext_function_returns_string(self): + """测试 gettext_function 返回字符串""" + result = gettext_function("test message") + assert isinstance(result, str) + + def test_ngettext_function_singular(self): + """测试 ngettext 单数形式""" + result = ngettext_function("one item", "many items", 1) + assert isinstance(result, str) + + def test_ngettext_function_plural(self): + """测试 ngettext 复数形式""" + result = ngettext_function("one item", "many items", 10) + assert isinstance(result, str)