Repository: iamkun/dayjs
Pull request #2411 (2023-08-12 19:07:54)

fix: change back fa locale to the Gregorian calendar equivalent
This PR (#2151) is getting it wrong by simply translating months to Solar Hijri calendar but not taking care of solar calendar's date and time transformations. Dayjs' locale provides literal translations of the Gregory calendar's months which are correct. I believe the referenced PR should be reverted to avoid misunderstanding.

Fixes #2151

amirhmoradi: @iamkun hi, 
I agree with this PR by @raminjafary 
Imho, it shall probably be in the next release. 

Being a farsi (persian) native speaker I confirm the PR as it translates month names, as is the system is intended to do in i18n files. 

I created the dayjs calendar systems plugin (https://github.com/calidy-com/dayjs-calendarsystems) to address calendar system conversions between all (gregory and non-gregorian) calendars. 
mokhosh: This is a must merge, would be great to have this sooner.

Patch:
diff --git a/docs/ko/README-ko.md b/docs/ko/README-ko.md
index 4389cc3..cb1f77a 100644
--- a/docs/ko/README-ko.md
+++ b/docs/ko/README-ko.md
@@ -23,7 +23,7 @@
     </a>
 </p>
 
-> Day.js는 Moment.js와 호환되는 대부분의 API를 사용하며, 최신 브라우저에서 날짜와 시간에 대한 구문 분석, 유효성 검사, 조작, 출력하는 경량 JavaScript 라이브러리입니다. Moment.js를 사용하고 있다면, Day.js는 껌입니다.
+> Day.js는 대부분의 API가 Moment.js와 호환되며 최신 브라우저에서 날짜와 시간에 대한 구문 분석, 유효성 검사, 조작, 출력을 간편하게 처리하는 경량 JavaScript 라이브러리 입니다. Moment.js를 사용해본 경험이 있다면, Day.js도 쉽게 사용하실 수 있습니다.
 
 ```js
 dayjs()
@@ -46,7 +46,7 @@ dayjs()
 
 ### 문서
 
-더 많은 세부 사항과 API, 그리고 다른 문서를 [day.js.org](https://day.js.org/) 웹사이트에서 볼 수 있습니다.
+더 많은 세부 사항과 API 및 다른 문서는 [day.js.org](https://day.js.org/) 웹사이트에서 확인하실 수 있습니다.
 
 ### 설치
 
@@ -58,7 +58,7 @@ npm install dayjs --save
 
 ### API
 
-Day.js API를 사용해서 날짜와 시간에 대한 구문 분석, 유효성 검사, 조작, 출력을 쉽게 할 수 있습니다.
+Day.js API를 사용하여 날짜와 시간의 구문 분석, 검증, 조작, 출력을 간편하게 처리할 수 있습니다.
 
 ```javascript
 dayjs('2018-08-08') // parse
@@ -78,9 +78,9 @@ dayjs().isBefore(dayjs()) // query
 
 ### I18n
 
-Day.js는 국제화에 대해 많은 지원을 합니다.
+Day.js는 국제화에 대한 많은 지원을 제공하고 있습니다.
 
-그러나 그것을 사용하지 않는다면, 그 누구도 당신의 빌드에 포함되지 않습니다.
+하지만 이 기능을 사용하지 않는 경우, 빌드에 포함되지 않습니다.
 
 ```javascript
 import 'dayjs/locale/es' // load on demand
@@ -96,7 +96,7 @@ dayjs('2018-05-05')
 
 ### Plugin
 
-플러그인은 기능을 확장하거나 새로운 기능을 추가하기 위해 Day.js에 추가할 수 있는 독립적인 모듈입니다.
+플러그인은 Day.js의 기능을 확장하거나 새로운 기능을 도입하기 위한 독립적인 모듈입니다.
 
 ```javascript
 import advancedFormat from 'dayjs/plugin/advancedFormat' // load on demand
diff --git a/src/locale/ar.js b/src/locale/ar.js
index a7025c4..f418379 100644
--- a/src/locale/ar.js
+++ b/src/locale/ar.js
@@ -2,6 +2,7 @@
 import dayjs from 'dayjs'
 
 const months = 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_')
+
 const symbolMap = {
   1: '١',
   2: '٢',
@@ -36,6 +37,7 @@ const locale = {
   months,
   monthsShort: months,
   weekStart: 6,
+  meridiem: hour => (hour > 12 ? 'م' : 'ص'),
   relativeTime: {
     future: 'بعد %s',
     past: 'منذ %s',
diff --git a/src/locale/fa.js b/src/locale/fa.js
index cbcd13c..cf33827 100644
--- a/src/locale/fa.js
+++ b/src/locale/fa.js
@@ -4,11 +4,11 @@ import dayjs from 'dayjs'
 const locale = {
   name: 'fa',
   weekdays: 'یک‌شنبه_دوشنبه_سه‌شنبه_چهارشنبه_پنج‌شنبه_جمعه_شنبه'.split('_'),
-  weekdaysShort: 'یک‌_دو_سه‌_چه_پن_جم_شن'.split('_'),
+  weekdaysShort: 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'),
   weekdaysMin: 'ی_د_س_چ_پ_ج_ش'.split('_'),
   weekStart: 6,
-  months: 'فروردین_اردیبهشت_خرداد_تیر_مرداد_شهریور_مهر_آبان_آذر_دی_بهمن_اسفند'.split('_'),
-  monthsShort: 'فرو_ارد_خرد_تیر_مرد_شهر_مهر_آبا_آذر_دی_بهم_اسف'.split('_'),
+  months: 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'),
+  monthsShort: 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'),
   ordinal: n => n,
   formats: {
     LT: 'HH:mm',
@@ -20,7 +20,7 @@ const locale = {
   },
   relativeTime: {
     future: 'در %s',
-    past: '%s قبل',
+    past: '%s پیش',
     s: 'چند ثانیه',
     m: 'یک دقیقه',
     mm: '%d دقیقه',
diff --git a/src/locale/ko.js b/src/locale/ko.js
index 8a8e8ea..7053e83 100644
--- a/src/locale/ko.js
+++ b/src/locale/ko.js
@@ -8,7 +8,7 @@ const locale = {
   weekdaysMin: '일_월_화_수_목_금_토'.split('_'),
   months: '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'),
   monthsShort: '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'),
-  ordinal: n => n,
+  ordinal: n => `${n}일`,
   formats: {
     LT: 'A h:mm',
     LTS: 'A h:mm:ss',
diff --git a/src/locale/sl.js b/src/locale/sl.js
index f09e3bf..1e0e4f8 100644
--- a/src/locale/sl.js
+++ b/src/locale/sl.js
@@ -1,6 +1,71 @@
 // Slovenian [sl]
 import dayjs from 'dayjs'
 
+function dual(n) {
+  return (n % 100) == 2 // eslint-disable-line
+}
+
+function threeFour(n) {
+  return (n % 100) == 3 || (n % 100) == 4 // eslint-disable-line
+}
+
+/* eslint-disable */
+function translate(number, withoutSuffix, key, isFuture) {
+  const result = `${number} `
+  switch (key) {
+    case 's': // a few seconds / in a few seconds / a few seconds ago
+      return (withoutSuffix || isFuture) ? 'nekaj sekund' : 'nekaj sekundami'
+    case 'm': // a minute / in a minute / a minute ago
+      return withoutSuffix ? 'ena minuta' : 'eno minuto'
+    case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago
+      if (dual(number)) {
+        return result + ((withoutSuffix || isFuture) ? 'minuti' : 'minutama')
+      }
+      if (threeFour(number)) {
+        return result + ((withoutSuffix || isFuture) ? 'minute' : 'minutami')
+      }
+      return result + ((withoutSuffix || isFuture) ? 'minut' : 'minutami')
+    case 'h': // an hour / in an hour / an hour ago
+      return withoutSuffix ? 'ena ura' : (isFuture ? 'eno uro' : 'eno uro')
+    case 'hh': // 9 hours / in 9 hours / 9 hours ago
+      if (dual(number)) {
+        return result + ((withoutSuffix || isFuture) ? 'uri' : 'urama')
+      }
+      if (threeFour(number)) {
+        return result + ((withoutSuffix || isFuture) ? 'ure' : 'urami')
+      }
+      return result + ((withoutSuffix || isFuture) ? 'ur' : 'urami')
+    case 'd': // a day / in a day / a day ago
+      return (withoutSuffix || isFuture) ? 'en dan' : 'enim dnem'
+    case 'dd': // 9 days / in 9 days / 9 days ago
+      if (dual(number)) {
+        return result + ((withoutSuffix || isFuture) ? 'dneva' : 'dnevoma')
+      }
+      return result + ((withoutSuffix || isFuture) ? 'dni' : 'dnevi')
+    case 'M': // a month / in a month / a month ago
+      return (withoutSuffix || isFuture) ? 'en mesec' : 'enim mesecem'
+    case 'MM': // 9 months / in 9 months / 9 months ago
+      if (dual(number)) { // 2 minutes / in 2 minutes
+        return result + ((withoutSuffix || isFuture) ? 'meseca' : 'mesecema')
+      }
+      if (threeFour(number)) {
+        return result + ((withoutSuffix || isFuture) ? 'mesece' : 'meseci')
+      }
+      return result + ((withoutSuffix || isFuture) ? 'mesecev' : 'meseci')
+    case 'y': // a year / in a year / a year ago
+      return (withoutSuffix || isFuture) ? 'eno leto' : 'enim letom'
+    case 'yy': // 9 years / in 9 years / 9 years ago
+      if (dual(number)) { // 2 minutes / in 2 minutes
+        return result + ((withoutSuffix || isFuture) ? 'leti' : 'letoma')
+      }
+      if (threeFour(number)) {
+        return result + ((withoutSuffix || isFuture) ? 'leta' : 'leti')
+      }
+      return result + ((withoutSuffix || isFuture) ? 'let' : 'leti')
+  }
+}
+
+/* eslint-enable */
 const locale = {
   name: 'sl',
   weekdays: 'nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota'.split('_'),
@@ -16,26 +81,26 @@ const locale = {
     L: 'DD.MM.YYYY',
     LL: 'D. MMMM YYYY',
     LLL: 'D. MMMM YYYY H:mm',
-    LLLL: 'dddd, D. MMMM YYYY H:mm'
+    LLLL: 'dddd, D. MMMM YYYY H:mm',
+    l: 'D. M. YYYY'
   },
   relativeTime: {
     future: 'čez %s',
     past: 'pred %s',
-    s: 'nekaj sekund',
-    m: 'minuta',
-    mm: '%d minut',
-    h: 'ura',
-    hh: '%d ur',
-    d: 'dan',
-    dd: '%d dni',
-    M: 'mesec',
-    MM: '%d mesecev',
-    y: 'leto',
-    yy: '%d let'
+    s: translate,
+    m: translate,
+    mm: translate,
+    h: translate,
+    hh: translate,
+    d: translate,
+    dd: translate,
+    M: translate,
+    MM: translate,
+    y: translate,
+    yy: translate
   }
 }
 
 dayjs.locale(locale, null, true)
 
 export default locale
-


Test patch:
diff --git a/test/locale/ar.test.js b/test/locale/ar.test.js
index 3f9f60f..2b5a57d 100644
--- a/test/locale/ar.test.js
+++ b/test/locale/ar.test.js
@@ -50,3 +50,13 @@ it('RelativeTime: Time from X gets formatted', () => {
       .toBe(t[2])
   })
 })
+
+it('Format meridiem with locale function', () => {
+  for (let i = 0; i <= 23; i += 1) {
+    const hour = dayjs()
+      .startOf('day')
+      .add(i, 'hour')
+    const meridiem = i > 12 ? 'م' : 'ص'
+    expect(hour.locale('ar').format('A')).toBe(`${meridiem}`)
+  }
+})
diff --git a/test/locale/sl.test.js b/test/locale/sl.test.js
new file mode 100644
index 0000000..672d606
--- /dev/null
+++ b/test/locale/sl.test.js
@@ -0,0 +1,82 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import relativeTime from '../../src/plugin/relativeTime'
+import '../../src/locale/sl'
+
+dayjs.extend(relativeTime)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Slovenian locale relative time in past and future', () => {
+  const cases = [
+    [1, 's', 'čez nekaj sekund', 'nekaj sekund'],
+    [-1, 's', 'pred nekaj sekundami', 'nekaj sekund'],
+    [1, 'm', 'čez eno minuto', 'ena minuta'],
+    [-1, 'm', 'pred eno minuto', 'ena minuta'],
+    [2, 'm', 'čez 2 minuti', '2 minuti'],
+    [-2, 'm', 'pred 2 minutama', '2 minuti'],
+    [3, 'm', 'čez 3 minute', '3 minute'],
+    [-3, 'm', 'pred 3 minutami', '3 minute'],
+    [5, 'm', 'čez 5 minut', '5 minut'],
+    [-5, 'm', 'pred 5 minutami', '5 minut'],
+    [1, 'h', 'čez eno uro', 'ena ura'],
+    [-1, 'h', 'pred eno uro', 'ena ura'],
+    [2, 'h', 'čez 2 uri', '2 uri'],
+    [-2, 'h', 'pred 2 urama', '2 uri'],
+    [3, 'h', 'čez 3 ure', '3 ure'],
+    [-3, 'h', 'pred 3 urami', '3 ure'],
+    [5, 'h', 'čez 5 ur', '5 ur'],
+    [-5, 'h', 'pred 5 urami', '5 ur'],
+    [1, 'd', 'čez en dan', 'en dan'],
+    [-1, 'd', 'pred enim dnem', 'en dan'],
+    [2, 'd', 'čez 2 dneva', '2 dneva'],
+    [-2, 'd', 'pred 2 dnevoma', '2 dneva'],
+    [3, 'd', 'čez 3 dni', '3 dni'],
+    [-3, 'd', 'pred 3 dnevi', '3 dni'],
+    [5, 'd', 'čez 5 dni', '5 dni'],
+    [-5, 'd', 'pred 5 dnevi', '5 dni'],
+    [1, 'M', 'čez en mesec', 'en mesec'],
+    [-1, 'M', 'pred enim mesecem', 'en mesec'],
+    [2, 'M', 'čez 2 meseca', '2 meseca'],
+    [-2, 'M', 'pred 2 mesecema', '2 meseca'],
+    [3, 'M', 'čez 3 mesece', '3 mesece'],
+    [-3, 'M', 'pred 3 meseci', '3 mesece'],
+    [5, 'M', 'čez 5 mesecev', '5 mesecev'],
+    [-5, 'M', 'pred 5 meseci', '5 mesecev'],
+    [1, 'y', 'čez eno leto', 'eno leto'],
+    [-1, 'y', 'pred enim letom', 'eno leto'],
+    [2, 'y', 'čez 2 leti', '2 leti'],
+    [-2, 'y', 'pred 2 letoma', '2 leti'],
+    [3, 'y', 'čez 3 leta', '3 leta'],
+    [-3, 'y', 'pred 3 leti', '3 leta'],
+    [5, 'y', 'čez 5 let', '5 let'],
+    [-5, 'y', 'pred 5 leti', '5 let']
+    // these are rounded
+    // if user decides to change rounding then it would be good to test them also
+    // [102, 's', 'čez 102 sekundi', '102 sekundi'],
+    // [-102, 's', 'pred 102 sekundama', '102 sekundi'],
+    // [103, 's', 'čez 103 sekunde', '103 sekunde'],
+    // [-103, 's', 'pred 103 sekundami', '103 sekunde'],
+    // [114, 's', 'čez 114 sekund', '114 sekund'],
+    // [-114, 's', 'pred 114 sekundami', '114 sekund'],
+    // [-102, 'm', 'čez 102 minuti', '102 minuti'],
+    // [-102, 'm', 'pred 102 minutama', '102 minuti'],
+    // [103, 'm', 'čez 103 minute', '103 minute'],
+    // [-103, 'm', 'pred 103 minutami', '103 minute'],
+    // [114, 'm', 'čez 114 minut', '114 minut'],
+    // [-114, 'm', 'pred 114 minutami', '114 minut']
+  ]
+
+  cases.forEach((c) => {
+    // With suffix
+    expect(dayjs().add(c[0], c[1]).locale('sl').fromNow()).toBe(c[2])
+    // Without suffix
+    expect(dayjs().add(c[0], c[1]).locale('sl').fromNow(true)).toBe(c[3])
+  })
+})