# Pull Request Template
## Description
This PR enhances the pagination component with standardized number
formatting and improved i18n handling.
**Includes:**
* Added `formatCompactNumber` and `formatFullNumber` helpers using
`Intl.NumberFormat`.
* `< 1,000`: show exact value (e.g., `999`)
* `1,000–999,999`: show compact format (`1k`, `1k+`)
* `1,000,000+`: show in millions with one decimal (e.g., `1.2M`)
* Updated `PaginationFooter` to use the new formatters for all displayed
numbers.
* Added proper pluralization to pagination i18n strings.
Fixes
https://linear.app/chatwoot/issue/CW-5999/better-display-of-numbers
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
### Screenshoots
**Before**
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/9fcf8baa-ae32-4a8a-85b0-24002fd863db"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/3d7138b7-133e-4ae6-b55f-67eff73ff1cc"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/1bbf7070-0681-492d-9308-a33874052d28"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/4e441672-26aa-4e66-965e-9edb807eaa72"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/11836702-1b74-4834-8932-31c20adc2db8"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/d37971bc-09af-4238-8601-ccc2ae69dbe7"
/>
**After**
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/8eaf2a23-beea-486b-b555-37f8b36ab904"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/f44f508a-e39d-45cb-afd8-98deb26920f8"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/d3b90711-bd7e-44ee-8bb3-48e45b799420"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/30dca6cd-f2be-4dcb-8596-924326ebf8c0"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/58896699-1f05-46c9-88cb-908318e71476"
/>
<img width="991" height="69" alt="image"
src="https://github.com/user-attachments/assets/ea0d91b0-077b-4d72-81a7-d38d17742da6"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
68 lines
2.4 KiB
JavaScript
68 lines
2.4 KiB
JavaScript
import { useI18n } from 'vue-i18n';
|
|
|
|
/**
|
|
* Composable for number formatting with i18n locale support
|
|
* Provides methods to format numbers in compact and full display formats
|
|
*/
|
|
export function useNumberFormatter() {
|
|
const { locale } = useI18n();
|
|
|
|
/**
|
|
* Formats numbers for display with clean, minimal formatting
|
|
* - Up to 1,000: show exact number (e.g., 999)
|
|
* - 1,000 to 999,999: show as "Xk" for exact thousands or "Xk+" for remainder (e.g., 1000 → "1k", 1500 → "1k+")
|
|
* - 1,000,000+: show in millions with 1 decimal place (e.g., 1,234,000 → "1.2M")
|
|
*
|
|
* Uses browser-native Intl.NumberFormat with proper i18n locale support
|
|
*
|
|
* @param {number} num - The number to format
|
|
* @returns {string} Formatted number string
|
|
*/
|
|
const formatCompactNumber = num => {
|
|
if (typeof num !== 'number' || Number.isNaN(num)) {
|
|
return '0';
|
|
}
|
|
|
|
// For numbers between -1000 and 1000 (exclusive), show exact number with locale formatting
|
|
if (Math.abs(num) < 1000) {
|
|
return new Intl.NumberFormat(locale.value).format(num);
|
|
}
|
|
|
|
// For numbers with absolute value above 1,000,000, show in millions with 1 decimal place
|
|
if (Math.abs(num) >= 1000000) {
|
|
const millions = num / 1000000;
|
|
return new Intl.NumberFormat(locale.value, {
|
|
notation: 'compact',
|
|
compactDisplay: 'short',
|
|
maximumFractionDigits: 1,
|
|
minimumFractionDigits: millions % 1 === 0 ? 0 : 1,
|
|
}).format(num);
|
|
}
|
|
|
|
// For numbers with absolute value between 1,000 and 1,000,000, show as "Xk" or "Xk+" using floor value
|
|
// For negative numbers, we want to floor towards zero (truncate), not towards negative infinity
|
|
const thousands = num >= 0 ? Math.floor(num / 1000) : Math.ceil(num / 1000);
|
|
const remainder = Math.abs(num) % 1000;
|
|
const suffix = remainder === 0 ? 'k' : 'k+';
|
|
return `${new Intl.NumberFormat(locale.value).format(thousands)}${suffix}`;
|
|
};
|
|
|
|
/**
|
|
* Format a number for full display with locale-specific formatting
|
|
* @param {number} num - The number to format
|
|
* @returns {string} Formatted number string with full precision and locale formatting (e.g., 1,234,567)
|
|
*/
|
|
const formatFullNumber = num => {
|
|
if (typeof num !== 'number' || Number.isNaN(num)) {
|
|
return '0';
|
|
}
|
|
|
|
return new Intl.NumberFormat(locale.value).format(num);
|
|
};
|
|
|
|
return {
|
|
formatCompactNumber,
|
|
formatFullNumber,
|
|
};
|
|
}
|