diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 9a9228d..2915d39 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -3,6 +3,7 @@ import { withMermaid } from 'vitepress-plugin-mermaid' import llmstxt from 'vitepress-plugin-llms' import footnote from 'markdown-it-footnote' import mark from 'markdown-it-mark' +import { buildNav, buildSidebar } from './data/site-content.mjs' const rawBase = process.env.VITEPRESS_BASE const base = rawBase @@ -11,151 +12,6 @@ const base = rawBase : `/${rawBase}/` : '/' -// Shared sidebar configuration -const sharedSidebar = { - '/en/': [ - { - text: 'Getting Started', - items: [ - { text: 'Introduction', link: '/en/' }, - { text: 'Quick Start', link: '/en/guide/getting-started' }, - { text: 'Architecture', link: '/en/guide/architecture' }, - { text: 'Project Structure', link: '/en/guide/project-structure' }, - ], - }, - { - text: 'Academy', - items: [ - { text: 'Algorithm Academy', link: '/en/academy/' }, - { text: 'Huffman Coding', link: '/en/academy/huffman' }, - { text: 'State Machine Design', link: '/en/academy/state-machine' }, - ], - }, - { - text: 'Algorithms', - items: [ - { text: 'Overview', link: '/en/guide/algorithms' }, - { text: 'Huffman Coding', link: '/en/algorithms/huffman' }, - { text: 'Arithmetic Coding', link: '/en/algorithms/arithmetic' }, - { text: 'Range Coder', link: '/en/algorithms/range' }, - { text: 'Run-Length Encoding', link: '/en/algorithms/rle' }, - ], - }, - { - text: 'API Reference', - items: [ - { text: 'Streaming API', link: '/en/api/streaming' }, - { text: 'Go Library', link: '/en/api/go' }, - { text: 'Rust Crate', link: '/en/api/rust' }, - { text: 'C++ Header', link: '/en/api/cpp' }, - ], - }, - { - text: 'Benchmarks & Testing', - items: [ - { text: 'Performance Results', link: '/en/benchmarks/results' }, - { text: 'How to Run', link: '/en/benchmarks/how-to-run' }, - { text: 'Cross-Language Testing', link: '/en/testing/cross-language' }, - ], - }, - { - text: 'Reference', - items: [ - { text: 'Architecture Design', link: '/en/architecture/' }, - { text: 'Bibliography', link: '/en/reference/bibliography' }, - { text: 'OpenSpec Specs', link: 'https://github.com/LessUp/compress-kit/tree/master/openspec/specs' }, - { text: 'Contributing', link: '/en/guide/contributing' }, - { text: 'Changelog', link: '/en/release-notes/changelog' }, - ], - }, - ], - '/zh/': [ - { - text: '开始使用', - items: [ - { text: '项目介绍', link: '/zh/' }, - { text: '快速开始', link: '/zh/guide/getting-started' }, - { text: '架构设计', link: '/zh/guide/architecture' }, - { text: '项目结构', link: '/zh/guide/project-structure' }, - ], - }, - { - text: '学院', - items: [ - { text: '算法学院', link: '/zh/academy/' }, - { text: '霍夫曼编码深度解析', link: '/zh/academy/huffman' }, - { text: '状态机设计哲学', link: '/zh/academy/state-machine' }, - ], - }, - { - text: '算法详解', - items: [ - { text: '算法综述', link: '/zh/guide/algorithms' }, - { text: '霍夫曼编码', link: '/zh/algorithms/huffman' }, - { text: '算术编码', link: '/zh/algorithms/arithmetic' }, - { text: '区间编码', link: '/zh/algorithms/range' }, - { text: '行程编码', link: '/zh/algorithms/rle' }, - ], - }, - { - text: 'API 参考', - items: [ - { text: 'Streaming API', link: '/zh/api/streaming' }, - { text: 'Go 库', link: '/zh/api/go' }, - { text: 'Rust 包', link: '/zh/api/rust' }, - { text: 'C++ 头文件', link: '/zh/api/cpp' }, - ], - }, - { - text: '基准测试', - items: [ - { text: '性能结果', link: '/zh/benchmarks/results' }, - { text: '如何运行', link: '/zh/benchmarks/how-to-run' }, - { text: '跨语言测试', link: '/zh/testing/cross-language' }, - ], - }, - { - text: '参考', - items: [ - { text: '系统架构设计', link: '/zh/architecture/' }, - { text: '参考文献', link: '/zh/reference/bibliography' }, - { text: 'OpenSpec 规范', link: 'https://github.com/LessUp/compress-kit/tree/master/openspec/specs' }, - { text: '参与贡献', link: '/zh/guide/contributing' }, - { text: '更新日志', link: '/zh/release-notes/changelog' }, - ], - }, - ], -} - -// Shared nav configuration -const sharedNav = (lang: string) => [ - { - text: lang === 'zh' ? '首页' : 'Home', - link: lang === 'zh' ? '/zh/' : '/en/', - activeMatch: lang === 'zh' ? '^/zh/$' : '^/en/$' - }, - { - text: lang === 'zh' ? '开始' : 'Get Started', - link: lang === 'zh' ? '/zh/guide/getting-started' : '/en/guide/getting-started', - activeMatch: lang === 'zh' ? '/zh/guide/' : '/en/guide/' - }, - { - text: lang === 'zh' ? '算法' : 'Algorithms', - link: lang === 'zh' ? '/zh/guide/algorithms' : '/en/guide/algorithms', - activeMatch: lang === 'zh' ? '/zh/algorithms/' : '/en/algorithms/' - }, - { - text: 'API', - link: lang === 'zh' ? '/zh/api/go' : '/en/api/go', - activeMatch: lang === 'zh' ? '/zh/api/' : '/en/api/' - }, - { - text: lang === 'zh' ? '基准' : 'Benchmarks', - link: lang === 'zh' ? '/zh/benchmarks/results' : '/en/benchmarks/results', - activeMatch: lang === 'zh' ? '/zh/benchmarks/' : '/en/benchmarks/' - }, -] - export default withMermaid(defineConfig({ base, title: 'CompressKit', @@ -175,8 +31,8 @@ export default withMermaid(defineConfig({ lang: 'en-US', link: '/en/', themeConfig: { - nav: sharedNav('en'), - sidebar: sharedSidebar['/en/'], + nav: buildNav('en'), + sidebar: buildSidebar('en'), editLink: { pattern: 'https://github.com/LessUp/compress-kit/edit/master/docs/:path', text: 'Edit this page on GitHub', @@ -223,8 +79,8 @@ export default withMermaid(defineConfig({ lang: 'zh-CN', link: '/zh/', themeConfig: { - nav: sharedNav('zh'), - sidebar: sharedSidebar['/zh/'], + nav: buildNav('zh'), + sidebar: buildSidebar('zh'), editLink: { pattern: 'https://github.com/LessUp/compress-kit/edit/master/docs/:path', text: '在 GitHub 上编辑此页', diff --git a/docs/.vitepress/data/site-content.mjs b/docs/.vitepress/data/site-content.mjs new file mode 100644 index 0000000..7904bcb --- /dev/null +++ b/docs/.vitepress/data/site-content.mjs @@ -0,0 +1,366 @@ +const localeOrEnglish = locale => (locale === 'zh' ? 'zh' : 'en') + +const localize = (value, locale) => value[localeOrEnglish(locale)] ?? value.en + +const withLocale = (locale, link) => { + if (/^https?:\/\//.test(link)) { + return link + } + + const normalized = link === '/' ? '/' : `/${link.replace(/^\/+/, '')}` + const prefix = `/${localeOrEnglish(locale)}` + return normalized === '/' ? `${prefix}/` : `${prefix}${normalized}` +} + +export const algorithmCatalog = [ + { + id: 'huffman', + slug: 'huffman', + icon: '🌳', + name: { en: 'Huffman Coding', zh: '霍夫曼编码' }, + chartLabel: { en: 'Huffman', zh: 'Huffman' }, + description: { + en: 'Optimal prefix codes based on symbol frequency. The classic approach to lossless compression.', + zh: '基于符号频率的最优前缀码。经典的无损压缩方法。', + }, + compression: { en: 'Medium', zh: '中等' }, + speed: { en: 'Fast', zh: '快速' }, + compressionTag: { en: 'Fast Speed', zh: '速度快' }, + speedLevel: 'fast', + compressionLevel: 'medium', + bestFor: { + en: ['Text files', 'General data', 'Natural language'], + zh: ['文本文件', '通用数据', '自然语言'], + }, + }, + { + id: 'arithmetic', + slug: 'arithmetic', + icon: '🧮', + name: { en: 'Arithmetic Coding', zh: '算术编码' }, + chartLabel: { en: 'Arithmetic', zh: 'Arithmetic' }, + description: { + en: 'Entire message encoded as a single number. Achieves entropy limit for maximum compression.', + zh: '整个消息编码为单个数字。达到熵极限,实现最大压缩率。', + }, + compression: { en: 'High', zh: '高' }, + speed: { en: 'Medium', zh: '中速' }, + compressionTag: { en: 'High Compression', zh: '高压缩率' }, + speedLevel: 'medium', + compressionLevel: 'high', + bestFor: { + en: ['Maximum compression', 'Statistical data', 'Archival storage'], + zh: ['最大压缩率', '统计型数据', '归档存储'], + }, + }, + { + id: 'range', + slug: 'range', + icon: '🎯', + name: { en: 'Range Coder', zh: '区间编码' }, + chartLabel: { en: 'Range', zh: 'Range' }, + description: { + en: 'Integer-based arithmetic coding. Production-ready balance of speed and compression.', + zh: '基于整数的算术编码。生产级的速度与压缩率平衡。', + }, + compression: { en: 'High', zh: '高' }, + speed: { en: 'Fast', zh: '快速' }, + compressionTag: { en: 'Fast + High', zh: '快 + 高压缩' }, + speedLevel: 'fast', + compressionLevel: 'high', + bestFor: { + en: ['Production systems', 'Real-time compression', 'Balanced workloads'], + zh: ['生产系统', '实时压缩', '平衡型负载'], + }, + }, + { + id: 'rle', + slug: 'rle', + icon: '📏', + name: { en: 'Run-Length Encoding', zh: '行程编码' }, + chartLabel: { en: 'RLE', zh: 'RLE' }, + description: { + en: 'Simple and fast compression for repetitive data. Often used as preprocessing.', + zh: '针对重复数据的简单快速压缩。常作为预处理步骤使用。', + }, + compression: { en: 'Variable', zh: '可变' }, + speed: { en: 'Very Fast', zh: '极快' }, + compressionTag: { en: 'Very Fast', zh: '极快' }, + speedLevel: 'very-fast', + compressionLevel: 'variable', + bestFor: { + en: ['Bitmap images', 'Log files', 'Preprocessing step'], + zh: ['位图图像', '日志文件', '预处理步骤'], + }, + }, +] + +export const benchmarkCatalog = { + algorithms: algorithmCatalog.map(entry => ({ + id: entry.id, + label: entry.chartLabel, + })), + languages: [ + { id: 'cpp', color: '#667eea', label: { en: 'C++', zh: 'C++' } }, + { id: 'go', color: '#00add8', label: { en: 'Go', zh: 'Go' } }, + { id: 'rust', color: '#de6e4b', label: { en: 'Rust', zh: 'Rust' } }, + ], + datasets: [ + { id: 'textlike_10MiB', label: { en: 'Text-like (10 MiB)', zh: '类文本 (10 MiB)' } }, + { id: 'repetitive_10MiB', label: { en: 'Repetitive (10 MiB)', zh: '重复数据 (10 MiB)' } }, + { id: 'small_dictionary_like', label: { en: 'Small dictionary-like sample', zh: '小型词典型样本' } }, + ], + metrics: [ + { id: 'encodeSpeed', label: { en: 'Encode Speed (MiB/s)', zh: '编码速度 (MiB/s)' } }, + { id: 'decodeSpeed', label: { en: 'Decode Speed (MiB/s)', zh: '解码速度 (MiB/s)' } }, + { id: 'compressionRatio', label: { en: 'Size Saved Relative to Input', zh: '相对输入节省的体积' } }, + ], + metricOptions: [ + { id: 'encodeSpeed', label: { en: 'Encode Speed', zh: '编码速度' } }, + { id: 'decodeSpeed', label: { en: 'Decode Speed', zh: '解码速度' } }, + { id: 'compressionRatio', label: { en: 'Compression Ratio', zh: '压缩比' } }, + ], +} + +export const homepageFeatureCatalog = [ + ...algorithmCatalog.map(entry => ({ + id: entry.id, + kind: 'algorithm', + algorithmId: entry.id, + title: entry.name, + description: entry.description, + tags: [ + { label: { en: 'Learn More', zh: '了解更多' }, link: `/algorithms/${entry.slug}` }, + { label: entry.compressionTag }, + ], + })), + { + id: 'cross-language', + kind: 'guide', + title: { en: '🔄 Cross-Language', zh: '🔄 跨语言兼容' }, + description: { + en: 'Encode in one language, decode in another. All implementations produce identical binary output.', + zh: '一种语言编码,另一种语言解码。所有实现产生完全相同的二进制输出。', + }, + tags: [ + { label: { en: 'Get Started', zh: '快速开始' }, link: '/guide/getting-started' }, + { label: { en: 'Testing', zh: '测试' }, link: '/testing/cross-language' }, + ], + }, + { + id: 'benchmarks', + kind: 'guide', + title: { en: '📊 Benchmarks', zh: '📊 性能基准' }, + description: { + en: 'Performance benchmarks across all algorithms and languages. Compare speed and compression.', + zh: '跨所有算法和语言的性能基准测试。比较速度和压缩率。', + }, + tags: [ + { label: { en: 'View Results', zh: '查看结果' }, link: '/benchmarks/results' }, + { label: { en: 'Run Tests', zh: '运行测试' }, link: '/benchmarks/how-to-run' }, + ], + }, +] + +const navCatalog = [ + { + id: 'home', + text: { en: 'Home', zh: '首页' }, + link: '/', + activeMatch: { en: '^/en/$', zh: '^/zh/$' }, + }, + { + id: 'guide', + text: { en: 'Get Started', zh: '开始' }, + link: '/guide/getting-started', + activeMatch: { en: '/en/guide/', zh: '/zh/guide/' }, + }, + { + id: 'algorithms', + text: { en: 'Algorithms', zh: '算法' }, + link: '/guide/algorithms', + activeMatch: { en: '/en/algorithms/', zh: '/zh/algorithms/' }, + }, + { + id: 'api', + text: { en: 'API', zh: 'API' }, + link: '/api/go', + activeMatch: { en: '/en/api/', zh: '/zh/api/' }, + }, + { + id: 'benchmarks', + text: { en: 'Benchmarks', zh: '基准' }, + link: '/benchmarks/results', + activeMatch: { en: '/en/benchmarks/', zh: '/zh/benchmarks/' }, + }, +] + +const sidebarCatalog = [ + { + title: { en: 'Getting Started', zh: '开始使用' }, + items: [ + { text: { en: 'Introduction', zh: '项目介绍' }, link: '/' }, + { text: { en: 'Quick Start', zh: '快速开始' }, link: '/guide/getting-started' }, + { text: { en: 'Architecture', zh: '架构设计' }, link: '/guide/architecture' }, + { text: { en: 'Project Structure', zh: '项目结构' }, link: '/guide/project-structure' }, + ], + }, + { + title: { en: 'Academy', zh: '学院' }, + items: [ + { text: { en: 'Algorithm Academy', zh: '算法学院' }, link: '/academy/' }, + { text: { en: 'Huffman Coding', zh: '霍夫曼编码深度解析' }, link: '/academy/huffman' }, + { text: { en: 'State Machine Design', zh: '状态机设计哲学' }, link: '/academy/state-machine' }, + ], + }, + { + title: { en: 'Algorithms', zh: '算法详解' }, + items: [ + { text: { en: 'Overview', zh: '算法综述' }, link: '/guide/algorithms' }, + { text: { en: 'Huffman Coding', zh: '霍夫曼编码' }, link: '/algorithms/huffman' }, + { text: { en: 'Arithmetic Coding', zh: '算术编码' }, link: '/algorithms/arithmetic' }, + { text: { en: 'Range Coder', zh: '区间编码' }, link: '/algorithms/range' }, + { text: { en: 'Run-Length Encoding', zh: '行程编码' }, link: '/algorithms/rle' }, + ], + }, + { + title: { en: 'API Reference', zh: 'API 参考' }, + items: [ + { text: { en: 'Streaming API', zh: 'Streaming API' }, link: '/api/streaming' }, + { text: { en: 'Go Library', zh: 'Go 库' }, link: '/api/go' }, + { text: { en: 'Rust Crate', zh: 'Rust 包' }, link: '/api/rust' }, + { text: { en: 'C++ Header', zh: 'C++ 头文件' }, link: '/api/cpp' }, + ], + }, + { + title: { en: 'Benchmarks & Testing', zh: '基准测试' }, + items: [ + { text: { en: 'Performance Results', zh: '性能结果' }, link: '/benchmarks/results' }, + { text: { en: 'How to Run', zh: '如何运行' }, link: '/benchmarks/how-to-run' }, + { text: { en: 'Cross-Language Testing', zh: '跨语言测试' }, link: '/testing/cross-language' }, + ], + }, + { + title: { en: 'Reference', zh: '参考' }, + items: [ + { text: { en: 'Architecture Design', zh: '系统架构设计' }, link: '/architecture/' }, + { text: { en: 'Bibliography', zh: '参考文献' }, link: '/reference/bibliography' }, + { text: { en: 'OpenSpec Specs', zh: 'OpenSpec 规范' }, link: 'https://github.com/LessUp/compress-kit/tree/master/openspec/specs' }, + { text: { en: 'Contributing', zh: '参与贡献' }, link: '/guide/contributing' }, + { text: { en: 'Changelog', zh: '更新日志' }, link: '/release-notes/changelog' }, + ], + }, +] + +export function buildNav(locale) { + return navCatalog.map(item => ({ + text: localize(item.text, locale), + link: withLocale(locale, item.link), + activeMatch: localize(item.activeMatch, locale), + })) +} + +export function buildSidebar(locale) { + return sidebarCatalog.map(section => ({ + text: localize(section.title, locale), + items: section.items.map(item => ({ + text: localize(item.text, locale), + link: withLocale(locale, item.link), + })), + })) +} + +export function getAlgorithmCards(locale) { + return algorithmCatalog.map(entry => ({ + id: entry.id, + slug: entry.slug, + icon: entry.icon, + name: localize(entry.name, locale), + description: localize(entry.description, locale), + compression: localize(entry.compression, locale), + speed: localize(entry.speed, locale), + bestFor: localize(entry.bestFor, locale), + compressionLevel: entry.compressionLevel, + speedLevel: entry.speedLevel, + bestForLabel: localize({ en: 'Best for:', zh: '适合场景:' }, locale), + learnMoreLabel: localize({ en: 'Learn more', zh: '了解更多' }, locale), + compressionSuffix: localize({ en: 'Compression', zh: '压缩' }, locale), + speedSuffix: localize({ en: 'Speed', zh: '速度' }, locale), + })) +} + +export function getBenchmarkContent(locale) { + return { + algorithms: benchmarkCatalog.algorithms.map(entry => ({ + id: entry.id, + label: localize(entry.label, locale), + })), + languages: benchmarkCatalog.languages.map(entry => ({ + id: entry.id, + label: localize(entry.label, locale), + color: entry.color, + })), + datasets: benchmarkCatalog.datasets.map(entry => ({ + id: entry.id, + label: localize(entry.label, locale), + })), + metrics: benchmarkCatalog.metrics.map(entry => ({ + id: entry.id, + label: localize(entry.label, locale), + })), + metricOptions: benchmarkCatalog.metricOptions.map(entry => ({ + id: entry.id, + label: localize(entry.label, locale), + })), + title: localize({ en: 'Performance Comparison', zh: '性能对比' }, locale), + datasetLabel: localize({ en: 'Dataset:', zh: '数据集:' }, locale), + metricLabel: localize({ en: 'Metric:', zh: '指标:' }, locale), + compressionNote: localize( + { + en: 'Bars show size saved relative to input; labels show the actual output/input ratio, and the best ratio is highlighted.', + zh: '柱状图展示相对输入节省的体积,标签显示实际输出/输入比值,最佳压缩比会被高亮。', + }, + locale + ), + } +} + +export function getHomepageContent(locale) { + const currentLocale = localeOrEnglish(locale) + const alternateLocale = currentLocale === 'en' ? 'zh' : 'en' + const alternateLabel = currentLocale === 'en' ? '中文' : 'English' + + return { + subtitle: localize( + { en: 'Lossless Compression Library', zh: '无损压缩算法库' }, + currentLocale + ), + intro: localize( + { + en: 'CompressKit provides classic lossless compression algorithms with cross-language compatibility. Encode in C++, decode in Go. Encode in Rust, decode in C++. All implementations produce identical binary output.', + zh: 'CompressKit 提供经典的无损压缩算法,支持跨语言兼容。C++ 编码,Go 解码。Rust 编码,C++ 解码。所有实现产生完全相同的二进制输出。', + }, + currentLocale + ), + sections: { + algorithms: localize({ en: 'Algorithms', zh: '算法' }, currentLocale), + quickStart: localize({ en: 'Quick Start', zh: '快速开始' }, currentLocale), + }, + quickStartCommand: 'git clone https://github.com/LessUp/compress-kit.git && cd compress-kit && make build && make test', + stats: ['C++17', 'Go', 'Rust'], + navLinks: [ + { text: localize({ en: 'Get Started', zh: '快速开始' }, currentLocale), link: withLocale(currentLocale, '/guide/getting-started') }, + { text: 'GitHub', link: 'https://github.com/LessUp/compress-kit' }, + { text: alternateLabel, link: withLocale(alternateLocale, '/') }, + ], + featureCards: homepageFeatureCatalog.map(entry => ({ + id: entry.id, + title: localize(entry.title, currentLocale), + description: localize(entry.description, currentLocale), + tags: entry.tags.map(tag => ({ + label: localize(tag.label, currentLocale), + link: tag.link ? withLocale(currentLocale, tag.link) : null, + })), + })), + } +} diff --git a/docs/.vitepress/theme/components/AlgorithmGrid.vue b/docs/.vitepress/theme/components/AlgorithmGrid.vue index bfffa73..0bae1e8 100644 --- a/docs/.vitepress/theme/components/AlgorithmGrid.vue +++ b/docs/.vitepress/theme/components/AlgorithmGrid.vue @@ -1,75 +1,13 @@ + + diff --git a/docs/.vitepress/theme/components/LanguageDetector.vue b/docs/.vitepress/theme/components/LanguageDetector.vue deleted file mode 100644 index 2273ca0..0000000 --- a/docs/.vitepress/theme/components/LanguageDetector.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index f72126d..1a7637a 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,6 +1,6 @@ import type { Theme } from 'vitepress' import DefaultTheme from 'vitepress/theme' -import { h, onMounted, watch, nextTick } from 'vue' +import { onMounted, watch, nextTick } from 'vue' import { useData } from 'vitepress' import './styles/vars.css' @@ -8,15 +8,9 @@ import './styles/vars.css' import AlgorithmGrid from './components/AlgorithmGrid.vue' import BenchmarkChart from './components/BenchmarkChart.vue' import CodeComparison from './components/CodeComparison.vue' -import LanguageDetector from './components/LanguageDetector.vue' export default { extends: DefaultTheme, - Layout: () => { - return h(DefaultTheme.Layout, null, { - 'layout-bottom': () => h(LanguageDetector), - }) - }, enhanceApp({ app }) { // Register custom components globally app.component('AlgorithmGrid', AlgorithmGrid) diff --git a/docs/.vitepress/theme/utils/language-preference.mjs b/docs/.vitepress/theme/utils/language-preference.mjs new file mode 100644 index 0000000..3d86d90 --- /dev/null +++ b/docs/.vitepress/theme/utils/language-preference.mjs @@ -0,0 +1,64 @@ +export const LOCALE_STORAGE_KEY = 'compresskit-lang' + +export function normalizeBase(base = '/') { + if (!base || base === '/') { + return '/' + } + + const rooted = base.startsWith('/') ? base : `/${base}` + return rooted.endsWith('/') ? rooted : `${rooted}/` +} + +export function normalizeLocale(locale) { + if (!locale) { + return null + } + + const normalized = String(locale).trim().toLowerCase() + if (normalized.startsWith('zh')) { + return 'zh' + } + if (normalized.startsWith('en')) { + return 'en' + } + return null +} + +export function buildLocalePath(base, locale) { + return `${normalizeBase(base)}${locale}/` +} + +export function readSavedLocale(storage) { + if (!storage?.getItem) { + return null + } + return normalizeLocale(storage.getItem(LOCALE_STORAGE_KEY)) +} + +export function persistLocale(storage, locale) { + const normalized = normalizeLocale(locale) + if (!normalized || !storage?.setItem) { + return null + } + storage.setItem(LOCALE_STORAGE_KEY, normalized) + return normalized +} + +export function getPreferredLocale({ savedLocale, browserLanguage }) { + return normalizeLocale(savedLocale) ?? normalizeLocale(browserLanguage) ?? 'en' +} + +function isRootLandingPath(pathname, base) { + const normalizedBase = normalizeBase(base) + const trimmedBase = normalizedBase === '/' ? '/' : normalizedBase.slice(0, -1) + return pathname === normalizedBase || pathname === trimmedBase +} + +export function getLandingRedirectTarget({ pathname, base = '/', savedLocale, browserLanguage }) { + if (!isRootLandingPath(pathname, base)) { + return null + } + + const locale = getPreferredLocale({ savedLocale, browserLanguage }) + return buildLocalePath(base, locale) +} diff --git a/docs/en/index.md b/docs/en/index.md index 7c6951e..5b39f57 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -4,115 +4,14 @@ layout: home -
-
- -
- CompressKit - Lossless Compression Library -
-
-
- Get Started - GitHub - 中文 -
-
- -
-
- CompressKit provides classic lossless compression algorithms with cross-language compatibility. Encode in C++, decode in Go. Encode in Rust, decode in C++. All implementations produce identical binary output. -
-
- C++17 - Go - Rust -
-
- -## Algorithms - -
-
-
🌳 Huffman Coding
-
- Optimal prefix codes based on symbol frequency. The classic approach to lossless compression. -
-
- Learn More - Fast Speed -
-
- -
-
🧮 Arithmetic Coding
-
- Entire message encoded as a single number. Achieves entropy limit for maximum compression. -
-
- Learn More - High Compression -
-
- -
-
🎯 Range Coder
-
- Integer-based arithmetic coding. Production-ready balance of speed and compression. -
-
- Learn More - Fast + High -
-
- -
-
📏 Run-Length Encoding
-
- Simple and fast compression for repetitive data. Often used as preprocessing. -
-
- Learn More - Very Fast -
-
- -
-
🔄 Cross-Language
-
- Encode in one language, decode in another. All implementations produce identical binary output. -
- -
- -
-
📊 Benchmarks
-
- Performance benchmarks across all algorithms and languages. Compare speed and compression. -
- -
-
- -
-
Quick Start
-
-
- git clone https://github.com/LessUp/compress-kit.git && cd compress-kit && make build && make test -
-
-
+ diff --git a/docs/index.md b/docs/index.md index 3877e71..41a7322 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,24 +15,21 @@ hero: diff --git a/docs/tests/language-preference.test.mjs b/docs/tests/language-preference.test.mjs new file mode 100644 index 0000000..fb2473e --- /dev/null +++ b/docs/tests/language-preference.test.mjs @@ -0,0 +1,69 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { + LOCALE_STORAGE_KEY, + getLandingRedirectTarget, + normalizeBase, + normalizeLocale, +} from '../.vitepress/theme/utils/language-preference.mjs' + +test('normalizeBase produces rooted trailing-slash paths', () => { + assert.equal(normalizeBase('/compress-kit'), '/compress-kit/') + assert.equal(normalizeBase('compress-kit'), '/compress-kit/') + assert.equal(normalizeBase('/'), '/') +}) + +test('saved locale wins on root landing redirect', () => { + assert.equal( + getLandingRedirectTarget({ + pathname: '/compress-kit/', + base: '/compress-kit', + savedLocale: 'zh', + browserLanguage: 'en-US', + }), + '/compress-kit/zh/' + ) +}) + +test('browser language drives redirect when no saved locale exists', () => { + assert.equal( + getLandingRedirectTarget({ + pathname: '/compress-kit/', + base: '/compress-kit/', + savedLocale: null, + browserLanguage: 'zh-CN', + }), + '/compress-kit/zh/' + ) + + assert.equal( + getLandingRedirectTarget({ + pathname: '/compress-kit/', + base: '/compress-kit/', + savedLocale: null, + browserLanguage: 'fr-FR', + }), + '/compress-kit/en/' + ) +}) + +test('non-root pages do not trigger landing redirect', () => { + assert.equal( + getLandingRedirectTarget({ + pathname: '/compress-kit/en/guide/getting-started', + base: '/compress-kit/', + savedLocale: 'zh', + browserLanguage: 'zh-CN', + }), + null + ) +}) + +test('normalizeLocale accepts only shipped locales', () => { + assert.equal(LOCALE_STORAGE_KEY, 'compresskit-lang') + assert.equal(normalizeLocale('zh'), 'zh') + assert.equal(normalizeLocale('en-US'), 'en') + assert.equal(normalizeLocale('jp'), null) + assert.equal(normalizeLocale(''), null) +}) diff --git a/docs/tests/site-content.test.mjs b/docs/tests/site-content.test.mjs new file mode 100644 index 0000000..f832d65 --- /dev/null +++ b/docs/tests/site-content.test.mjs @@ -0,0 +1,61 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { + algorithmCatalog, + benchmarkCatalog, + buildNav, + buildSidebar, + homepageFeatureCatalog, +} from '../.vitepress/data/site-content.mjs' + +const stripLocalePrefix = link => link.replace(/^\/(en|zh)\//, '/') + +test('sidebar keeps one canonical link graph across locales', () => { + const english = buildSidebar('en') + const chinese = buildSidebar('zh') + + assert.equal(english.length, chinese.length, 'both locales should expose same sidebar sections') + + for (let index = 0; index < english.length; index += 1) { + const left = english[index] + const right = chinese[index] + assert.equal(left.items.length, right.items.length, `section ${index} should keep same item count`) + assert.deepEqual( + left.items.map(item => stripLocalePrefix(item.link)), + right.items.map(item => stripLocalePrefix(item.link)), + `section ${index} should map to same canonical destinations` + ) + } +}) + +test('top nav keeps one canonical destination set across locales', () => { + const english = buildNav('en') + const chinese = buildNav('zh') + + assert.deepEqual( + english.map(item => stripLocalePrefix(item.link)), + chinese.map(item => stripLocalePrefix(item.link)), + 'both locales should keep same nav destination order' + ) +}) + +test('homepage and benchmark metadata share one canonical algorithm catalog', () => { + const canonicalAlgorithms = algorithmCatalog.map(entry => entry.id).sort() + const homepageAlgorithms = homepageFeatureCatalog + .filter(entry => entry.kind === 'algorithm') + .map(entry => entry.algorithmId) + .sort() + const benchmarkAlgorithms = benchmarkCatalog.algorithms.map(entry => entry.id).sort() + + assert.deepEqual(homepageAlgorithms, canonicalAlgorithms, 'homepage algorithm cards should derive from canonical catalog') + assert.deepEqual(benchmarkAlgorithms, canonicalAlgorithms, 'benchmark labels should derive from canonical catalog') +}) + +test('benchmark catalog preserves shipped language order', () => { + assert.deepEqual( + benchmarkCatalog.languages.map(entry => entry.id), + ['cpp', 'go', 'rust'], + 'benchmark legend should stay aligned with shipped languages' + ) +}) diff --git a/docs/zh/index.md b/docs/zh/index.md index 150f4f8..c81de11 100644 --- a/docs/zh/index.md +++ b/docs/zh/index.md @@ -4,115 +4,14 @@ layout: home -
-
- -
- CompressKit - 无损压缩算法库 -
-
- -
- -
-
- CompressKit 提供经典的无损压缩算法,支持跨语言兼容。C++ 编码,Go 解码。Rust 编码,C++ 解码。所有实现产生完全相同的二进制输出。 -
-
- C++17 - Go - Rust -
-
- -## 算法 - -
-
-
🌳 霍夫曼编码
-
- 基于符号频率的最优前缀码。经典的无损压缩方法。 -
-
- 了解更多 - 速度快 -
-
- -
-
🧮 算术编码
-
- 整个消息编码为单个数字。达到熵极限,实现最大压缩率。 -
-
- 了解更多 - 高压缩率 -
-
- -
-
🎯 区间编码
-
- 基于整数的算术编码。生产级的速度与压缩率平衡。 -
-
- 了解更多 - 快 + 高压缩 -
-
- -
-
📏 行程编码
-
- 针对重复数据的简单快速压缩。常作为预处理步骤使用。 -
-
- 了解更多 - 极快 -
-
- -
-
🔄 跨语言兼容
-
- 一种语言编码,另一种语言解码。所有实现产生完全相同的二进制输出。 -
- -
- -
-
📊 性能基准
-
- 跨所有算法和语言的性能基准测试。比较速度和压缩率。 -
- -
-
- -
-
快速开始
-
-
- git clone https://github.com/LessUp/compress-kit.git && cd compress-kit && make build && make test -
-
-
+