diff --git a/src/locale/sr-cyrl.js b/src/locale/sr-cyrl.js index 019544dac..84b42addd 100644 --- a/src/locale/sr-cyrl.js +++ b/src/locale/sr-cyrl.js @@ -1,6 +1,42 @@ // Serbian Cyrillic [sr-cyrl] import dayjs from 'dayjs' +const translator = { + words: { + m: ['један минут', 'једног минута'], + mm: ['%d минут', '%d минута', '%d минута'], + h: ['један сат', 'једног сата'], + hh: ['%d сат', '%d сата', '%d сати'], + d: ['један дан', 'једног дана'], + dd: ['%d дан', '%d дана', '%d дана'], + M: ['један месец', 'једног месеца'], + MM: ['%d месец', '%d месеца', '%d месеци'], + y: ['једну годину', 'једне године'], + yy: ['%d годину', '%d године', '%d година'] + }, + correctGrammarCase(number, wordKey) { + if (number % 10 >= 1 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)) { + return number % 10 === 1 ? wordKey[0] : wordKey[1] + } + return wordKey[2] + }, + relativeTimeFormatter(number, withoutSuffix, key, isFuture) { + const wordKey = translator.words[key] + + if (key.length === 1) { + // Nominativ + if (key === 'y' && withoutSuffix) return 'једна година' + return isFuture || withoutSuffix ? wordKey[0] : wordKey[1] + } + + const word = translator.correctGrammarCase(number, wordKey) + // Nominativ + if (key === 'yy' && withoutSuffix && word === '%d годину') return `${number} година` + + return word.replace('%d', number) + } +} + const locale = { name: 'sr-cyrl', weekdays: 'Недеља_Понедељак_Уторак_Среда_Четвртак_Петак_Субота'.split('_'), @@ -12,26 +48,26 @@ const locale = { relativeTime: { future: 'за %s', past: 'пре %s', - s: 'секунда', - m: 'минут', - mm: '%d минута', - h: 'сат', - hh: '%d сати', - d: 'дан', - dd: '%d дана', - M: 'месец', - MM: '%d месеци', - y: 'година', - yy: '%d године' + s: 'неколико секунди', + m: translator.relativeTimeFormatter, + mm: translator.relativeTimeFormatter, + h: translator.relativeTimeFormatter, + hh: translator.relativeTimeFormatter, + d: translator.relativeTimeFormatter, + dd: translator.relativeTimeFormatter, + M: translator.relativeTimeFormatter, + MM: translator.relativeTimeFormatter, + y: translator.relativeTimeFormatter, + yy: translator.relativeTimeFormatter }, ordinal: n => `${n}.`, formats: { LT: 'H:mm', LTS: 'H:mm:ss', - L: 'DD.MM.YYYY', - LL: 'D. MMMM YYYY', - LLL: 'D. MMMM YYYY H:mm', - LLLL: 'dddd, D. MMMM YYYY H:mm' + L: 'D. M. YYYY.', + LL: 'D. MMMM YYYY.', + LLL: 'D. MMMM YYYY. H:mm', + LLLL: 'dddd, D. MMMM YYYY. H:mm' } } diff --git a/src/locale/sr.js b/src/locale/sr.js index ca0cb47fb..8399d8e48 100644 --- a/src/locale/sr.js +++ b/src/locale/sr.js @@ -1,6 +1,42 @@ // Serbian [sr] import dayjs from 'dayjs' +const translator = { + words: { + m: ['jedan minut', 'jednog minuta'], + mm: ['%d minut', '%d minuta', '%d minuta'], + h: ['jedan sat', 'jednog sata'], + hh: ['%d sat', '%d sata', '%d sati'], + d: ['jedan dan', 'jednog dana'], + dd: ['%d dan', '%d dana', '%d dana'], + M: ['jedan mesec', 'jednog meseca'], + MM: ['%d mesec', '%d meseca', '%d meseci'], + y: ['jednu godinu', 'jedne godine'], + yy: ['%d godinu', '%d godine', '%d godina'] + }, + correctGrammarCase(number, wordKey) { + if (number % 10 >= 1 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)) { + return number % 10 === 1 ? wordKey[0] : wordKey[1] + } + return wordKey[2] + }, + relativeTimeFormatter(number, withoutSuffix, key, isFuture) { + const wordKey = translator.words[key] + + if (key.length === 1) { + // Nominativ + if (key === 'y' && withoutSuffix) return 'jedna godina' + return isFuture || withoutSuffix ? wordKey[0] : wordKey[1] + } + + const word = translator.correctGrammarCase(number, wordKey) + // Nominativ + if (key === 'yy' && withoutSuffix && word === '%d godinu') return `${number} godina` + + return word.replace('%d', number) + } +} + const locale = { name: 'sr', weekdays: 'Nedelja_Ponedeljak_Utorak_Sreda_Četvrtak_Petak_Subota'.split('_'), @@ -12,26 +48,26 @@ const locale = { relativeTime: { future: 'za %s', past: 'pre %s', - s: 'sekunda', - m: 'minut', - mm: '%d minuta', - h: 'sat', - hh: '%d sati', - d: 'dan', - dd: '%d dana', - M: 'mesec', - MM: '%d meseci', - y: 'godina', - yy: '%d godine' + s: 'nekoliko sekundi', + m: translator.relativeTimeFormatter, + mm: translator.relativeTimeFormatter, + h: translator.relativeTimeFormatter, + hh: translator.relativeTimeFormatter, + d: translator.relativeTimeFormatter, + dd: translator.relativeTimeFormatter, + M: translator.relativeTimeFormatter, + MM: translator.relativeTimeFormatter, + y: translator.relativeTimeFormatter, + yy: translator.relativeTimeFormatter }, ordinal: n => `${n}.`, formats: { LT: 'H:mm', LTS: 'H:mm:ss', - L: 'DD.MM.YYYY', - LL: 'D. MMMM YYYY', - LLL: 'D. MMMM YYYY H:mm', - LLLL: 'dddd, D. MMMM YYYY H:mm' + L: 'D. M. YYYY.', + LL: 'D. MMMM YYYY.', + LLL: 'D. MMMM YYYY. H:mm', + LLLL: 'dddd, D. MMMM YYYY. H:mm' } } diff --git a/test/locale/sr-cyrl.test.js b/test/locale/sr-cyrl.test.js new file mode 100644 index 000000000..a72ed33ae --- /dev/null +++ b/test/locale/sr-cyrl.test.js @@ -0,0 +1,54 @@ +import MockDate from 'mockdate' +import dayjs from '../../src' +import relativeTime from '../../src/plugin/relativeTime' +import '../../src/locale/sr-cyrl' + +dayjs.extend(relativeTime) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +it('Serbian cyrillic locale relative time in past and future', () => { + const cases = [ + [1, 's', 'за неколико секунди', 'неколико секунди'], + [-1, 's', 'пре неколико секунди', 'неколико секунди'], + [4, 's', 'за неколико секунди', 'неколико секунди'], + [1, 'm', 'за један минут', 'један минут'], + [-1, 'm', 'пре једног минута', 'један минут'], + [4, 'm', 'за 4 минута', '4 минута'], + [5, 'm', 'за 5 минута', '5 минута'], + [21, 'm', 'за 21 минут', '21 минут'], + [1, 'h', 'за један сат', 'један сат'], + [-1, 'h', 'пре једног сата', 'један сат'], + [4, 'h', 'за 4 сата', '4 сата'], + [5, 'h', 'за 5 сати', '5 сати'], + [21, 'h', 'за 21 сат', '21 сат'], + [1, 'd', 'за један дан', 'један дан'], + [-1, 'd', 'пре једног дана', 'један дан'], + [4, 'd', 'за 4 дана', '4 дана'], + [5, 'd', 'за 5 дана', '5 дана'], + [21, 'd', 'за 21 дан', '21 дан'], + [1, 'M', 'за један месец', 'један месец'], + [-1, 'M', 'пре једног месеца', 'један месец'], + [4, 'M', 'за 4 месеца', '4 месеца'], + [5, 'M', 'за 5 месеци', '5 месеци'], + [10, 'M', 'за 10 месеци', '10 месеци'], + [1, 'y', 'за једну годину', 'једна година'], + [-1, 'y', 'пре једне године', 'једна година'], + [4, 'y', 'за 4 године', '4 године'], + [5, 'y', 'за 5 година', '5 година'], + [21, 'y', 'за 21 годину', '21 година'] + ] + + cases.forEach((c) => { + expect(dayjs().add(c[0], c[1]).locale('sr-cyrl').fromNow()).toBe(c[2]) + expect(dayjs().add(c[0], c[1]).locale('sr-cyrl').fromNow(true)).toBe(c[3]) + // TODO: compare to momentjs once logic and grammar are fixed there + }) +}) + diff --git a/test/locale/sr.test.js b/test/locale/sr.test.js new file mode 100644 index 000000000..c2ccb9f25 --- /dev/null +++ b/test/locale/sr.test.js @@ -0,0 +1,55 @@ +import MockDate from 'mockdate' +import dayjs from '../../src' +import relativeTime from '../../src/plugin/relativeTime' +import '../../src/locale/sr' + +dayjs.extend(relativeTime) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +it('Serbian locale relative time in past and future', () => { + const cases = [ + [1, 's', 'za nekoliko sekundi', 'nekoliko sekundi'], + [-1, 's', 'pre nekoliko sekundi', 'nekoliko sekundi'], + [4, 's', 'za nekoliko sekundi', 'nekoliko sekundi'], + [1, 'm', 'za jedan minut', 'jedan minut'], + [-1, 'm', 'pre jednog minuta', 'jedan minut'], + [4, 'm', 'za 4 minuta', '4 minuta'], + [5, 'm', 'za 5 minuta', '5 minuta'], + [21, 'm', 'za 21 minut', '21 minut'], + [1, 'h', 'za jedan sat', 'jedan sat'], + [-1, 'h', 'pre jednog sata', 'jedan sat'], + [4, 'h', 'za 4 sata', '4 sata'], + [5, 'h', 'za 5 sati', '5 sati'], + [21, 'h', 'za 21 sat', '21 sat'], + [1, 'd', 'za jedan dan', 'jedan dan'], + [-1, 'd', 'pre jednog dana', 'jedan dan'], + [4, 'd', 'za 4 dana', '4 dana'], + [5, 'd', 'za 5 dana', '5 dana'], + [21, 'd', 'za 21 dan', '21 dan'], + [1, 'M', 'za jedan mesec', 'jedan mesec'], + [-1, 'M', 'pre jednog meseca', 'jedan mesec'], + [4, 'M', 'za 4 meseca', '4 meseca'], + [5, 'M', 'za 5 meseci', '5 meseci'], + [10, 'M', 'za 10 meseci', '10 meseci'], + [1, 'y', 'za jednu godinu', 'jedna godina'], + [-1, 'y', 'pre jedne godine', 'jedna godina'], + [4, 'y', 'za 4 godine', '4 godine'], + [5, 'y', 'za 5 godina', '5 godina'], + [21, 'y', 'za 21 godinu', '21 godina'] + ] + + cases.forEach((c) => { + // With suffix + expect(dayjs().add(c[0], c[1]).locale('sr').fromNow()).toBe(c[2]) + // Without suffix + expect(dayjs().add(c[0], c[1]).locale('sr').fromNow(true)).toBe(c[3]) + // TODO: compare to momentjs once logic and grammar are fixed there + }) +})