Skip to content

Commit ea436c3

Browse files
authored
feat(i18n): format numbers with Intl.NumberFormat (#1149)
1 parent 16b012a commit ea436c3

15 files changed

Lines changed: 141 additions & 141 deletions

File tree

app/components/Code/DirectoryListing.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import type { PackageFileTree } from '#shared/types'
33
import type { RouteLocationRaw } from 'vue-router'
44
import { getFileIcon } from '~/utils/file-icons'
5-
import { formatBytes } from '~/utils/formatters'
65
76
const props = defineProps<{
87
tree: PackageFileTree[]
@@ -51,6 +50,8 @@ function getCodeRoute(nodePath?: string): RouteLocationRaw {
5150
params: { path: pathSegments as [string, ...string[]] },
5251
}
5352
}
53+
54+
const bytesFormatter = useBytesFormatter()
5455
</script>
5556

5657
<template>
@@ -107,7 +108,7 @@ function getCodeRoute(nodePath?: string): RouteLocationRaw {
107108
v-if="node.type === 'file' && node.size"
108109
class="text-end font-mono text-xs text-fg-subtle"
109110
>
110-
{{ formatBytes(node.size) }}
111+
{{ bytesFormatter.format(node.size) }}
111112
</span>
112113
</NuxtLink>
113114
</td>

app/components/Compare/PackageSelector.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const filteredResults = computed(() => {
5353
.filter(r => !packages.value.includes(r.name))
5454
})
5555
56+
const numberFormatter = useNumberFormatter()
57+
5658
function addPackage(name: string) {
5759
if (packages.value.length >= maxPackages.value) return
5860
if (packages.value.includes(name)) return
@@ -209,8 +211,8 @@ function handleBlur() {
209211
<p class="text-xs text-fg-subtle">
210212
{{
211213
$t('compare.selector.packages_selected', {
212-
count: packages.length,
213-
max: maxPackages,
214+
count: numberFormatter.format(packages.length),
215+
max: numberFormatter.format(maxPackages),
214216
})
215217
}}
216218
<span v-if="packages.length < 2">{{ $t('compare.selector.add_hint') }}</span>

app/components/Package/Card.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const pkgDescription = useMarkdown(() => ({
3434
plain: true,
3535
packageName: props.result.package.name,
3636
}))
37+
38+
const numberFormatter = useNumberFormatter()
3739
</script>
3840

3941
<template>
@@ -180,7 +182,7 @@ const pkgDescription = useMarkdown(() => ({
180182
class="text-fg-subtle text-xs pointer-events-auto"
181183
:title="result.package.keywords.slice(5).join(', ')"
182184
>
183-
+{{ result.package.keywords.length - 5 }}
185+
+{{ numberFormatter.format(result.package.keywords.length - 5) }}
184186
</span>
185187
</div>
186188
</BaseCard>

app/components/Package/Dependencies.vue

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const sortedOptionalDependencies = computed(() => {
6565
if (!props.optionalDependencies) return []
6666
return Object.entries(props.optionalDependencies).sort(([a], [b]) => a.localeCompare(b))
6767
})
68+
69+
const numberFormatter = useNumberFormatter()
6870
</script>
6971

7072
<template>
@@ -73,7 +75,11 @@ const sortedOptionalDependencies = computed(() => {
7375
<CollapsibleSection
7476
v-if="sortedDependencies.length > 0"
7577
id="dependencies"
76-
:title="$t('package.dependencies.title', { count: sortedDependencies.length })"
78+
:title="
79+
$t('package.dependencies.title', {
80+
count: numberFormatter.format(sortedDependencies.length),
81+
})
82+
"
7783
>
7884
<ul class="space-y-1 list-none m-0" :aria-label="$t('package.dependencies.list_label')">
7985
<li
@@ -138,7 +144,7 @@ const sortedOptionalDependencies = computed(() => {
138144
>
139145
{{
140146
$t('package.dependencies.show_all', {
141-
count: sortedDependencies.length,
147+
count: numberFormatter.format(sortedDependencies.length),
142148
})
143149
}}
144150
</button>
@@ -150,7 +156,7 @@ const sortedOptionalDependencies = computed(() => {
150156
id="peer-dependencies"
151157
:title="
152158
$t('package.peer_dependencies.title', {
153-
count: sortedPeerDependencies.length,
159+
count: numberFormatter.format(sortedPeerDependencies.length),
154160
})
155161
"
156162
>
@@ -186,7 +192,7 @@ const sortedOptionalDependencies = computed(() => {
186192
>
187193
{{
188194
$t('package.peer_dependencies.show_all', {
189-
count: sortedPeerDependencies.length,
195+
count: numberFormatter.format(sortedPeerDependencies.length),
190196
})
191197
}}
192198
</button>
@@ -198,7 +204,7 @@ const sortedOptionalDependencies = computed(() => {
198204
id="optional-dependencies"
199205
:title="
200206
$t('package.optional_dependencies.title', {
201-
count: sortedOptionalDependencies.length,
207+
count: numberFormatter.format(sortedOptionalDependencies.length),
202208
})
203209
"
204210
>

app/components/Package/DownloadAnalytics.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -750,8 +750,6 @@ const chartData = computed<{ dataset: VueUiXyDatasetItem[] | null; dates: number
750750
return { dataset, dates }
751751
})
752752
753-
const formatter = ({ value }: { value: number }) => formatCompactNumber(value, { decimals: 1 })
754-
755753
const loadFile = (link: string, filename: string) => {
756754
const a = document.createElement('a')
757755
a.href = link
@@ -800,6 +798,8 @@ function getGranularityLabel(granularity: ChartTimeGranularity) {
800798
return granularityLabels.value[granularity]
801799
}
802800
801+
const compactNumberFormatter = useCompactNumberFormatter()
802+
803803
// VueUiXy chart component configuration
804804
const chartConfig = computed(() => {
805805
return {
@@ -867,7 +867,7 @@ const chartConfig = computed(() => {
867867
},
868868
},
869869
yAxis: {
870-
formatter,
870+
formatter: compactNumberFormatter.value.format,
871871
useNiceScale: true,
872872
gap: 24, // vertical gap between individual series in stacked mode
873873
},
@@ -899,7 +899,7 @@ const chartConfig = computed(() => {
899899
.map((d: any) => {
900900
const label = String(d?.name ?? '').trim()
901901
const raw = Number(d?.value ?? 0)
902-
const v = formatter({ value: Number.isFinite(raw) ? raw : 0 })
902+
const v = compactNumberFormatter.value.format(Number.isFinite(raw) ? raw : 0)
903903
904904
if (!hasMultipleItems) {
905905
// We don't need the name of the package in this case, since it is shown in the xAxis label

app/components/Settings/TranslationHelper.vue

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const contributionGuideUrl =
2929
// Copy missing keys as JSON template to clipboard
3030
const { copy, copied } = useClipboard()
3131
32+
const numberFormatter = useNumberFormatter()
33+
const percentageFormatter = useNumberFormatter({ style: 'percent' })
34+
3235
function copyMissingKeysTemplate() {
3336
// Create a template showing what needs to be added
3437
const template = props.status.missingKeys.map(key => ` "${key}": ""`).join(',\n')
@@ -49,7 +52,10 @@ ${template}`
4952
<div class="flex items-center justify-between text-xs text-fg-muted">
5053
<span>{{ $t('settings.translation_progress') }}</span>
5154
<span class="tabular-nums"
52-
>{{ status.completedKeys }}/{{ status.totalKeys }} ({{ status.percentComplete }}%)</span
55+
>{{ numberFormatter.format(status.completedKeys) }}/{{
56+
numberFormatter.format(status.totalKeys)
57+
}}
58+
({{ percentageFormatter.format(status.percentComplete / 100) }})</span
5359
>
5460
</div>
5561
<div class="h-1.5 bg-bg rounded-full overflow-hidden">
@@ -64,7 +70,13 @@ ${template}`
6470
<div v-if="status.missingKeys.length > 0" class="space-y-2">
6571
<div class="flex items-center justify-between">
6672
<h4 class="text-xs text-fg-muted font-medium">
67-
{{ $t('i18n.missing_keys', { count: status.missingKeys.length }) }}
73+
{{
74+
$t(
75+
'i18n.missing_keys',
76+
{ count: numberFormatter.format(status.missingKeys.length) },
77+
status.missingKeys.length,
78+
)
79+
}}
6880
</h4>
6981
<button
7082
type="button"
@@ -87,7 +99,13 @@ ${template}`
8799
class="text-xs text-fg-muted hover:text-fg rounded focus-visible:outline-accent/70"
88100
@click="showAll = true"
89101
>
90-
{{ $t('i18n.show_more_keys', { count: remainingCount }) }}
102+
{{
103+
$t(
104+
'i18n.show_more_keys',
105+
{ count: numberFormatter.format(remainingCount) },
106+
remainingCount,
107+
)
108+
}}
91109
</button>
92110
</div>
93111

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export function useNumberFormatter(options?: Intl.NumberFormatOptions) {
2+
const { locale } = useI18n()
3+
4+
return computed(() => new Intl.NumberFormat(locale.value, options))
5+
}
6+
7+
export const useCompactNumberFormatter = () =>
8+
useNumberFormatter({
9+
notation: 'compact',
10+
compactDisplay: 'short',
11+
maximumFractionDigits: 1,
12+
})
13+
14+
export const useBytesFormatter = () => {
15+
const { t } = useI18n()
16+
const decimalNumberFormatter = useNumberFormatter({
17+
maximumFractionDigits: 1,
18+
})
19+
20+
return {
21+
format: (bytes: number) => {
22+
if (bytes < 1024)
23+
return t('package.size.b', {
24+
size: decimalNumberFormatter.value.format(bytes),
25+
})
26+
if (bytes < 1024 * 1024)
27+
return t('package.size.kb', {
28+
size: decimalNumberFormatter.value.format(bytes / 1024),
29+
})
30+
return t('package.size.mb', {
31+
size: decimalNumberFormatter.value.format(bytes / (1024 * 1024)),
32+
})
33+
},
34+
}
35+
}

app/composables/usePackageComparison.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import type { PackageLikes } from '#shared/types/social'
99
import { encodePackageName } from '#shared/utils/npm'
1010
import type { PackageAnalysisResponse } from './usePackageAnalysis'
1111
import { isBinaryOnlyPackage } from '#shared/utils/binary-detection'
12-
import { formatBytes } from '~/utils/formatters'
1312
import { getDependencyCount } from '~/utils/npm/dependency-count'
1413

1514
/** Special identifier for the "What Would James Do?" comparison column */
@@ -71,6 +70,9 @@ export interface PackageComparisonData {
7170
*/
7271
export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
7372
const { t } = useI18n()
73+
const numberFormatter = useNumberFormatter()
74+
const compactNumberFormatter = useCompactNumberFormatter()
75+
const bytesFormatter = useBytesFormatter()
7476
const packages = computed(() => toValue(packageNames))
7577

7678
// Cache of fetched data by package name (source of truth)
@@ -260,7 +262,14 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
260262

261263
return packagesData.value.map(pkg => {
262264
if (!pkg) return null
263-
return computeFacetValue(facet, pkg, t)
265+
return computeFacetValue(
266+
facet,
267+
pkg,
268+
numberFormatter.value.format,
269+
compactNumberFormatter.value.format,
270+
bytesFormatter.format,
271+
t,
272+
)
264273
})
265274
}
266275

@@ -342,6 +351,9 @@ function resolveNoDependencyDisplay(
342351
function computeFacetValue(
343352
facet: ComparisonFacet,
344353
data: PackageComparisonData,
354+
formatNumber: (num: number) => string,
355+
formatCompactNumber: (num: number) => string,
356+
formatBytes: (num: number) => string,
345357
t: (key: string, params?: Record<string, unknown>) => string,
346358
): FacetValue | null {
347359
const { isNoDependency } = data
@@ -513,7 +525,7 @@ function computeFacetValue(
513525
if (depCount == null) return null
514526
return {
515527
raw: depCount,
516-
display: String(depCount),
528+
display: formatNumber(depCount),
517529
status: depCount > 10 ? 'warning' : 'neutral',
518530
}
519531
}
@@ -532,7 +544,7 @@ function computeFacetValue(
532544
const totalDepCount = data.installSize.dependencyCount
533545
return {
534546
raw: totalDepCount,
535-
display: String(totalDepCount),
547+
display: formatNumber(totalDepCount),
536548
status: totalDepCount > 50 ? 'warning' : 'neutral',
537549
}
538550
}

app/pages/package-code/[...path].vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {
44
PackageFileTreeResponse,
55
PackageFileContentResponse,
66
} from '#shared/types'
7-
import { formatBytes } from '~/utils/formatters'
87
98
definePageMeta({
109
name: 'code',
@@ -269,6 +268,8 @@ const markdownViewModes = [
269268
270269
const markdownViewMode = shallowRef<(typeof markdownViewModes)[number]['key']>('preview')
271270
271+
const bytesFormatter = useBytesFormatter()
272+
272273
useHead({
273274
link: [{ rel: 'canonical', href: canonicalUrl }],
274275
})
@@ -443,7 +444,7 @@ defineOgImageComponent('Default', {
443444
$t('code.lines', { count: fileContent.lines })
444445
}}</span>
445446
<span v-if="currentNode?.size" class="text-fg-subtle">{{
446-
formatBytes(currentNode.size)
447+
bytesFormatter.format(currentNode.size)
447448
}}</span>
448449
</div>
449450
</div>
@@ -489,7 +490,9 @@ defineOgImageComponent('Default', {
489490
<div class="i-carbon:document w-12 h-12 mx-auto text-fg-subtle mb-4" />
490491
<p class="text-fg-muted mb-2">{{ $t('code.file_too_large') }}</p>
491492
<p class="text-fg-subtle text-sm mb-4">
492-
{{ $t('code.file_size_warning', { size: formatBytes(currentNode?.size ?? 0) }) }}
493+
{{
494+
$t('code.file_size_warning', { size: bytesFormatter.format(currentNode?.size ?? 0) })
495+
}}
493496
</p>
494497
<LinkBase
495498
variant="button-secondary"

0 commit comments

Comments
 (0)