diff --git a/.changeset/gorgeous-numbers-hear.md b/.changeset/gorgeous-numbers-hear.md new file mode 100644 index 0000000..41ca94a --- /dev/null +++ b/.changeset/gorgeous-numbers-hear.md @@ -0,0 +1,5 @@ +--- +"enhanced-ms": minor +--- + +Add support for Polish, Czech, and Chinese (Simplified) diff --git a/README.md b/README.md index fdbaf57..e740fd8 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,19 @@ As mentioned above, `enhanced-ms` is an enhanced version of the popular [`ms`](h The currently supported languages include: -| Language | Key | -| ----------------- | --- | -| English (default) | en | -| German | de | -| Russian | ru | -| Māori | mi | -| Spanish | es | -| Dutch | nl | -| Italian | it | -| French | fr | +| Language | Key | +| --------------------- | ----- | +| English (default) | en | +| German | de | +| Russian | ru | +| Māori | mi | +| Spanish | es | +| Dutch | nl | +| Italian | it | +| French | fr | +| Czech | cs | +| Polish | pl | +| Chinese (Simplified) | zh-CN | You can help by adding support for more languages. diff --git a/src/languages/cs.ts b/src/languages/cs.ts new file mode 100644 index 0000000..6dbdf65 --- /dev/null +++ b/src/languages/cs.ts @@ -0,0 +1,100 @@ +type Unit = keyof typeof UnitsMap; +const UnitsMap = { + nanosekundy: ['nanosekunda', 'nanosekundy', 'nanosekund'], + mikrosekundy: ['mikrosekunda', 'mikrosekundy', 'mikrosekund'], + milisekundy: ['milisekunda', 'milisekundy', 'milisekund'], + sekundy: ['sekunda', 'sekundy', 'sekund'], + minuty: ['minuta', 'minuty', 'minut'], + hodiny: ['hodina', 'hodiny', 'hodin'], + dny: ['den', 'dny', 'dní'], + týdny: ['týden', 'týdny', 'týdnů'], + měsíce: ['měsíc', 'měsíce', 'měsíců'], + roky: ['rok', 'roky', 'let'], + dekády: ['dekáda', 'dekády', 'dekád'], + století: ['století', 'století', 'století'], + tisíciletí: ['tisíciletí', 'tisíciletí', 'tisíciletí'], +} as const; + +function formatTime(unit: Unit, count: number) { + const words = UnitsMap[unit]; + + let word: string; + if (count === 1) word = words[0]!; + else if (count >= 2 && count <= 4) word = words[1]!; + else word = words[2]!; + + return word || ''; +} + +export default { + decimal: ',', + and: 'a', + + units: { + nanosecond: { + name: (c) => formatTime('nanosekundy', c), + abbreviation: 'ns', + matches: ['ns', 'nanosekunda', 'nanosekundy', 'nanosekund'], + }, + microsecond: { + name: (c) => formatTime('mikrosekundy', c), + abbreviation: 'μs', + matches: ['μs', 'us', 'mikrosekunda', 'mikrosekundy', 'mikrosekund'], + }, + millisecond: { + name: (c) => formatTime('milisekundy', c), + abbreviation: 'ms', + matches: ['ms', 'milisekunda', 'milisekundy', 'milisekund'], + }, + second: { + name: (c) => formatTime('sekundy', c), + abbreviation: 's', + matches: ['s', 'sek', 'sekunda', 'sekundy', 'sekund'], + }, + minute: { + name: (c) => formatTime('minuty', c), + abbreviation: 'min', + matches: ['m', 'min', 'minuta', 'minuty', 'minut'], + }, + hour: { + name: (c) => formatTime('hodiny', c), + abbreviation: 'h', + matches: ['h', 'hod', 'hodina', 'hodiny', 'hodin'], + }, + day: { + name: (c) => formatTime('dny', c), + abbreviation: 'd', + matches: ['d', 'den', 'dny', 'dní'], + }, + week: { + name: (c) => formatTime('týdny', c), + abbreviation: 'týd.', + matches: ['w', 'týd', 'týden', 'týdny', 'týdnů'], + }, + month: { + name: (c) => formatTime('měsíce', c), + abbreviation: 'měs.', + matches: ['mo', 'měs', 'měsíc', 'měsíce', 'měsíců'], + }, + year: { + name: (c) => formatTime('roky', c), + abbreviation: 'r.', + matches: ['y', 'r', 'rok', 'roky', 'let'], + }, + decade: { + name: (c) => formatTime('dekády', c), + abbreviation: 'dek.', + matches: ['dek', 'dekáda', 'dekády', 'dekád'], + }, + century: { + name: (c) => formatTime('století', c), + abbreviation: 'stol.', + matches: ['c', 'stol', 'století'], + }, + millennium: { + name: (c) => formatTime('tisíciletí', c), + abbreviation: 'tis.', + matches: ['mil', 'tis', 'tisíciletí'], + }, + }, +} satisfies import('./helpers/definition-types').LanguageDefinition; diff --git a/src/languages/index.ts b/src/languages/index.ts index 3018c0c..1e6c73b 100644 --- a/src/languages/index.ts +++ b/src/languages/index.ts @@ -1,5 +1,6 @@ import type { LanguageDefinition } from './helpers/definition-types'; +import cs from './cs'; import de from './de'; import en from './en'; import es from './es'; @@ -7,9 +8,34 @@ import fr from './fr'; import it from './it'; import mi from './mi'; import nl from './nl'; +import pl from './pl'; import ru from './ru'; +import zhCN from './zh-CN'; // This prevents the whole language definition being included in the dts output -type Locale = 'de' | 'en' | 'es' | 'fr' | 'it' | 'mi' | 'nl' | 'ru'; +type Locale = + | 'cs' + | 'de' + | 'en' + | 'es' + | 'fr' + | 'it' + | 'mi' + | 'nl' + | 'pl' + | 'ru' + | 'zh-CN'; type Languages = Record; -export const languages: Languages = { de, en, es, fr, it, mi, nl, ru }; +export const languages: Languages = { + cs, + de, + en, + es, + fr, + it, + mi, + nl, + pl, + ru, + 'zh-CN': zhCN, +}; diff --git a/src/languages/pl.ts b/src/languages/pl.ts new file mode 100644 index 0000000..70a71ec --- /dev/null +++ b/src/languages/pl.ts @@ -0,0 +1,104 @@ +type Unit = keyof typeof UnitsMap; +const UnitsMap = { + nanosekundy: ['nanosekunda', 'nanosekundy', 'nanosekund'], + mikrosekundy: ['mikrosekunda', 'mikrosekundy', 'mikrosekund'], + milisekundy: ['milisekunda', 'milisekundy', 'milisekund'], + sekundy: ['sekunda', 'sekundy', 'sekund'], + minuty: ['minuta', 'minuty', 'minut'], + godziny: ['godzina', 'godziny', 'godzin'], + dni: ['dzień', 'dni', 'dni'], + tygodnie: ['tydzień', 'tygodnie', 'tygodni'], + miesiące: ['miesiąc', 'miesiące', 'miesięcy'], + lata: ['rok', 'lata', 'lat'], + dekady: ['dekada', 'dekady', 'dekad'], + wieki: ['wiek', 'wieki', 'wieków'], + tysiąclecia: ['tysiąclecie', 'tysiąclecia', 'tysiącleci'], +} as const; + +function formatTime(unit: Unit, count: number) { + const words = UnitsMap[unit]; + + const lastDigit = count % 10; + const lastTwoDigits = count % 100; + + let word: string; + if (lastTwoDigits >= 11 && lastTwoDigits <= 19) word = words[2]!; + else if (lastDigit === 1) word = words[0]!; + else if (lastDigit >= 2 && lastDigit <= 4) word = words[1]!; + else word = words[2]!; + + return word || ''; +} + +export default { + decimal: ',', + and: 'i', + + units: { + nanosecond: { + name: (c) => formatTime('nanosekundy', c), + abbreviation: 'ns', + matches: ['ns', 'nanosekunda', 'nanosekundy', 'nanosekund'], + }, + microsecond: { + name: (c) => formatTime('mikrosekundy', c), + abbreviation: 'μs', + matches: ['μs', 'us', 'mikrosekunda', 'mikrosekundy', 'mikrosekund'], + }, + millisecond: { + name: (c) => formatTime('milisekundy', c), + abbreviation: 'ms', + matches: ['ms', 'milisekunda', 'milisekundy', 'milisekund'], + }, + second: { + name: (c) => formatTime('sekundy', c), + abbreviation: 's', + matches: ['s', 'sek', 'sekunda', 'sekundy', 'sekund'], + }, + minute: { + name: (c) => formatTime('minuty', c), + abbreviation: 'min', + matches: ['m', 'min', 'minuta', 'minuty', 'minut'], + }, + hour: { + name: (c) => formatTime('godziny', c), + abbreviation: 'godz.', + matches: ['h', 'godz', 'godzina', 'godziny', 'godzin'], + }, + day: { + name: (c) => formatTime('dni', c), + abbreviation: 'd', + matches: ['d', 'dzień', 'dni'], + }, + week: { + name: (c) => formatTime('tygodnie', c), + abbreviation: 'tydz.', + matches: ['w', 'tydz', 'tydzień', 'tygodnie', 'tygodni'], + }, + month: { + name: (c) => formatTime('miesiące', c), + abbreviation: 'mies.', + matches: ['mo', 'mies', 'miesiąc', 'miesiące', 'miesięcy'], + }, + year: { + name: (c) => formatTime('lata', c), + abbreviation: 'r.', + matches: ['y', 'r', 'rok', 'lata', 'lat'], + }, + decade: { + name: (c) => formatTime('dekady', c), + abbreviation: 'dek.', + matches: ['dek', 'dekada', 'dekady', 'dekad'], + }, + century: { + name: (c) => formatTime('wieki', c), + abbreviation: 'w.', + matches: ['c', 'wiek', 'wieki', 'wieków'], + }, + millennium: { + name: (c) => formatTime('tysiąclecia', c), + abbreviation: 'tys.', + matches: ['mil', 'tys', 'tysiąclecie', 'tysiąclecia', 'tysiącleci'], + }, + }, +} satisfies import('./helpers/definition-types').LanguageDefinition; diff --git a/src/languages/zh-CN.ts b/src/languages/zh-CN.ts new file mode 100644 index 0000000..4dafa15 --- /dev/null +++ b/src/languages/zh-CN.ts @@ -0,0 +1,71 @@ +export default { + decimal: '.', + and: '和', + units: { + nanosecond: { + name: '纳秒', + abbreviation: 'ns', + matches: ['纳秒', 'ns'], + }, + microsecond: { + name: '微秒', + abbreviation: 'μs', + matches: ['微秒', 'μs', 'us'], + }, + millisecond: { + name: '毫秒', + abbreviation: 'ms', + matches: ['毫秒', 'ms'], + }, + second: { + name: '秒', + abbreviation: 's', + matches: ['秒', 's'], + }, + minute: { + name: '分钟', + abbreviation: '分', + matches: ['分钟', '分', 'm', 'min'], + }, + hour: { + name: '小时', + abbreviation: '时', + matches: ['小时', '时', 'h', 'hour'], + }, + day: { + name: '天', + abbreviation: '天', + matches: ['天', '日', 'd', 'day'], + }, + week: { + name: '周', + abbreviation: '周', + matches: ['周', '星期', 'w', 'week'], + }, + month: { + name: '个月', + abbreviation: '月', + matches: ['个月', '月', 'mo', 'month'], + }, + year: { + name: '年', + abbreviation: '年', + matches: ['年', 'y', 'year'], + }, + decade: { + name: '十年', + abbreviation: '十年', + matches: ['十年', 'dec', 'decade'], + }, + century: { + name: '世纪', + abbreviation: '世纪', + matches: ['世纪', 'c', 'century'], + }, + millennium: { + name: '千年', + abbreviation: '千年', + matches: ['千年', 'mil', 'millennium'], + }, + }, +} satisfies import('./helpers/definition-types').LanguageDefinition; diff --git a/tests/cs.test.ts b/tests/cs.test.ts new file mode 100644 index 0000000..8ff06b3 --- /dev/null +++ b/tests/cs.test.ts @@ -0,0 +1,34 @@ +import { createMs } from 'enhanced-ms'; +import { describe, expect, it } from 'vitest'; + +const ms = createMs({ language: 'cs' }); + +describe('Czech (Čeština)', () => { + describe('format milliseconds', () => { + it('formats with correct pluralization', () => { + // 1 second -> sekunda + expect(ms(1000)).toBe('1 sekunda'); + // 2 seconds -> sekundy + expect(ms(2000)).toBe('2 sekundy'); + // 4 seconds -> sekundy + expect(ms(4000)).toBe('4 sekundy'); + // 5 seconds -> sekund + expect(ms(5000)).toBe('5 sekund'); + }); + + it('formats with proper unit names', () => { + // 1 day 2 hours 5 minutes + const time = 86400000 + 2 * 3600000 + 5 * 60000; + expect(ms(time)).toBe('1 den 2 hodiny 5 minut'); + }); + }); + + describe('parse duration', () => { + it('parses Czech duration strings', () => { + expect(ms('1 sekunda')).toBe(1000); + expect(ms('2 sekundy')).toBe(2000); + expect(ms('5 sekund')).toBe(5000); + expect(ms('1 den 2 hodiny')).toBe(86400000 + 2 * 3600000); + }); + }); +}); diff --git a/tests/pl.test.ts b/tests/pl.test.ts new file mode 100644 index 0000000..518c1d4 --- /dev/null +++ b/tests/pl.test.ts @@ -0,0 +1,38 @@ +import { createMs } from 'enhanced-ms'; +import { describe, expect, it } from 'vitest'; + +const ms = createMs({ language: 'pl' }); + +describe('Polish (Polski)', () => { + describe('format milliseconds', () => { + it('formats with correct pluralization', () => { + // 1 second -> sekunda + expect(ms(1000)).toBe('1 sekunda'); + // 2 seconds -> sekundy + expect(ms(2000)).toBe('2 sekundy'); + // 5 seconds -> sekund + expect(ms(5000)).toBe('5 sekund'); + // 12 seconds -> sekund (teen) + expect(ms(12000)).toBe('12 sekund'); + // 22 seconds -> sekundy (ends in 2 but not teen) + expect(ms(22000)).toBe('22 sekundy'); + // 25 seconds -> sekund + expect(ms(25000)).toBe('25 sekund'); + }); + + it('formats with proper unit names', () => { + // 1 day 2 hours 5 minutes + const time = 86400000 + 2 * 3600000 + 5 * 60000; + expect(ms(time)).toBe('1 dzień 2 godziny 5 minut'); + }); + }); + + describe('parse duration', () => { + it('parses Polish duration strings', () => { + expect(ms('1 sekunda')).toBe(1000); + expect(ms('2 sekundy')).toBe(2000); + expect(ms('5 sekund')).toBe(5000); + expect(ms('1 dzień 2 godziny')).toBe(86400000 + 2 * 3600000); + }); + }); +}); diff --git a/tests/zh-CN.test.ts b/tests/zh-CN.test.ts new file mode 100644 index 0000000..68994ce --- /dev/null +++ b/tests/zh-CN.test.ts @@ -0,0 +1,33 @@ +import { createMs } from 'enhanced-ms'; +import { describe, expect, it } from 'vitest'; + +const ms = createMs({ language: 'zh-CN' }); + +describe('Chinese Simplified (中文简体)', () => { + describe('format milliseconds', () => { + it('formats seconds correctly', () => { + expect(ms(1000)).toBe('1 秒'); + expect(ms(5000)).toBe('5 秒'); + }); + + it('formats with proper unit names', () => { + // 1 day 2 hours 5 minutes + const time = 86400000 + 2 * 3600000 + 5 * 60000; + expect(ms(time)).toBe('1 天 2 小时 5 分钟'); + }); + + it('formats hours and minutes', () => { + expect(ms(3600000)).toBe('1 小时'); + expect(ms(60000)).toBe('1 分钟'); + expect(ms(3660000)).toBe('1 小时 1 分钟'); + }); + }); + + describe('parse duration', () => { + it('parses Chinese duration strings', () => { + expect(ms('1 秒')).toBe(1000); + expect(ms('5 秒')).toBe(5000); + expect(ms('1 天 2 小时')).toBe(86400000 + 2 * 3600000); + }); + }); +});