Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions app/pages/package-timeline/[[org]]/[packageName].vue
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,19 @@ if (import.meta.client) {

const bytesFormatter = useBytesFormatter()

// Format a duration (in days) into a localized human-readable string
function formatDuration(days: number): string {
if (days < 30) return $t('package.timeline.time_gap_days', { count: days }, days)
if (days < 365)
return $t(
'package.timeline.time_gap_months',
{ count: Math.floor(days / 30) },
Math.floor(days / 30),
)
const years = Math.floor(days / 365)
return $t('package.timeline.time_gap_years', { count: years }, years)
}
Comment thread
btea marked this conversation as resolved.

// Detect notable changes between consecutive versions (size, license, ESM, types)
// Versions are compared against their semver predecessor, not chronological neighbor,
// so interleaved legacy releases don't produce misleading cross-line diffs.
Expand All @@ -189,11 +202,32 @@ const versionSubEvents = computed(() => {
}

for (const current of entries) {
const previous = prevBySemver.get(current.version)
if (!previous) continue

const events: SubEvent[] = []

// Time gap from the previous chronological release
// entries are sorted newest-first by publish time, so index+1 is the predecessor
const chronologicalIndex = entries.indexOf(current)
const prevRelease = entries[chronologicalIndex + 1]
if (prevRelease) {
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000
const gapMs = Date.parse(current.time) - Date.parse(prevRelease.time)
if (gapMs > SEVEN_DAYS_MS) {
const gapDays = Math.floor(gapMs / (1000 * 60 * 60 * 24))
events.push({
key: 'timeGap',
positive: false,
icon: 'i-lucide:clock',
text: $t('package.timeline.time_gap', { duration: formatDuration(gapDays) }),
})
}
}

const previous = prevBySemver.get(current.version)
if (!previous) {
if (events.length) result.set(current.version, events)
continue
}

// Size changes
const currentSize = sizeCache.get(sizeKey(current.version))
const previousSize = sizeCache.get(sizeKey(previous.version))
Expand Down
4 changes: 4 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,10 @@
"trusted_publisher_removed": "Trusted publishing removed",
"provenance_added": "Provenance enabled",
"provenance_removed": "Provenance removed",
"time_gap": "{duration} after previous release",
"time_gap_days": "{count} day | {count} days",
"time_gap_months": "{count} month | {count} months",
"time_gap_years": "{count} year | {count} years",
"chart": {
"tab_aria_label": "Metric selection",
"dependency_size": "Dependency Size",
Expand Down
6 changes: 5 additions & 1 deletion i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,11 @@
"version_events": "版本 {version}:{events}",
"general_description": "折线图展示了 {package} 包从版本 {first} 到 {last} 的 {metric} 变化情况。版本 {first} 的 {metric} 为 {first_value},版本 {last} 为 {last_value}(整体变化 {overall_progress_percentage}%)。{key_changes} {watermark}。"
}
}
},
"time_gap": "距上一版本 {duration}",
"time_gap_days": "{count} 天",
"time_gap_months": "{count} 个月",
"time_gap_years": "{count} 年"
},
"dependencies": {
"title": "依赖({count} 个)",
Expand Down
6 changes: 5 additions & 1 deletion i18n/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,11 @@
"timeline": {
"chart": {
"copy_alt": {}
}
},
"time_gap": "距上一版本 {duration}",
"time_gap_days": "{count} 天",
"time_gap_months": "{count} 個月",
"time_gap_years": "{count} 年"
},
"dependencies": {
"title": "相依({count} 個)",
Expand Down
12 changes: 12 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,18 @@
"provenance_removed": {
"type": "string"
},
"time_gap": {
"type": "string"
},
"time_gap_days": {
"type": "string"
},
"time_gap_months": {
"type": "string"
},
"time_gap_years": {
"type": "string"
},
"chart": {
"type": "object",
"properties": {
Expand Down
Loading