Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

i18nContributionGuide.md 28 KiB

3 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. # I18n Contribution Guide
  2. ## Table of Contents
  3. - [Adding a new locale](#adding-a-new-locale)
  4. - [Choosing a directory name for a locale](#choosing-a-directory-name-for-a-locale)
  5. - [index.js](#index.js)
  6. - [localize](#localize)
  7. - [localize.ordinalNumber](#localize.ordinalnumber)
  8. - [localize.era and using buildLocalizeFn function](#localize.era-and-using-buildlocalizefn-function)
  9. - [Formatting localizers](#formatting-localizers)
  10. - [localize.quarter](#localize.quarter)
  11. - [localize.month](#localize.month)
  12. - [localize.day](#localize.day)
  13. - [localize.dayPeriod](#localize.dayperiod)
  14. - [formatLong](#formatlong)
  15. - [formatLong.dateFormats](#formatlong.dateformats)
  16. - [formatLong.timeFormats](#formatlong.timeformats)
  17. - [formatLong.dateTimeFormats](#formatlong.datetimeformats)
  18. - [formatRelative](#formatrelative)
  19. - [match](#match)
  20. - [formatDistance](#formatdistance)
  21. - [Tests](#tests)
  22. - [Creating a locale with the same language as another locale](#creating-a-locale-with-the-same-language-as-another-locale)
  23. ## Adding a new locale
  24. To add a new locale:
  25. - [Choose a directory name for it](#choosing-a-directory-name-for-a-locale).
  26. - Copy the content of an existing locale (e.g. `en-US`) into the newly created directory.
  27. - Replace the values in the content with yours file-by-file.
  28. Use [CLDR data](https://www.unicode.org/cldr/charts/32/summary/root.html)
  29. as a point of reference which values to choose.
  30. All locales contain a number of properties:
  31. - [`formatDistance`](#formatdistance) — distance localizer function used by `formatDistance` and `formatDistanceStrict`.
  32. - [`formatLong`](#formatlong) — contains long date localizer functions used by `format` and `formatRelative`.
  33. - [`formatRelative`](#formatrelative) — relative date localizer function used by `formatRelative`.
  34. - [`localize`](#localize) — contains functions, which localize the various date values. Required by `format` and `formatRelative`.
  35. - [`match`](#match) — contains functions to parse date values. Required by `parse`.
  36. - [`options`](#indexjs) — contains the index of the first day of the week for functions such as `startOfWeek`,
  37. and the value which determines the first week of the year
  38. for functions like `setWeek`.
  39. ### Choosing a directory name for a locale
  40. Use the four letter code for the directory name (e.g. `en-GB`),
  41. Use the two/three letter code:
  42. - if the language code and the country code are the same (e.g. `pt` instead of `pt-PT`).
  43. - if the language is used in only one country (e.g. `fil` instead of `fil-PH`).
  44. - if all countries who use the language
  45. also use the same regional standards: the first day of the week,
  46. the week numbering (see: https://en.wikipedia.org/wiki/Week#Week_numbering),
  47. calendar date format (see: https://en.wikipedia.org/wiki/Calendar_date)
  48. and date representation (see: https://en.wikipedia.org/wiki/Date_and_time_representation_by_country
  49. and: https://en.wikipedia.org/wiki/Date_format_by_country)
  50. (e.g. `ca` instead of `ca-ES` and `ca-AD`).
  51. ### index.js
  52. Locale's `index.js` is where all the properties of the locale are combined in a single file,
  53. documented in JSDoc format.
  54. ```javascript
  55. import formatDistance from './_lib/formatDistance/index.js'
  56. import formatLong from './_lib/formatLong/index.js'
  57. import formatRelative from './_lib/formatRelative/index.js'
  58. import localize from './_lib/localize/index.js'
  59. import match from './_lib/match/index.js'
  60. /**
  61. * @type {Locale}
  62. * @category Locales
  63. *
  64. * // Name of the locale.
  65. * // Inside the parentheses - name of the country - if the locale uses the four letter code, e.g. en-US, fr-CA or pt-BR.
  66. * @summary English locale (United States).
  67. *
  68. * // Name of the language (used by https://date-fns.org/ website)
  69. * @language English
  70. *
  71. * // ISO 639-2 code. See the list here:
  72. * // https://www.loc.gov/standards/iso639-2/php/code_list.php
  73. * // Used by https://date-fns.org/ to detect the list of the countries that uses the language.
  74. * @iso-639-2 eng
  75. *
  76. * // Authors of the locale (including anyone who corrected or fixed the locale)
  77. * @author Sasha Koss [@kossnocorp]{@link https://github.com/kossnocorp}
  78. * @author Lesha Koss [@leshakoss]{@link https://github.com/leshakoss}
  79. */
  80. var locale = {
  81. code: 'en',
  82. formatDistance: formatDistance,
  83. formatLong: formatLong,
  84. formatRelative: formatRelative,
  85. localize: localize,
  86. match: match,
  87. options: {
  88. // Index of the first day of the week.
  89. // Sunday is 0, Monday is 1, Saturday is 6.
  90. weekStartsOn: 0,
  91. // Nth of January which is always in the first week of the year. See:
  92. // https://en.wikipedia.org/wiki/Week#Week_numbering
  93. // http://www.pjh2.de/datetime/weeknumber/wnd.php?l=en
  94. firstWeekContainsDate: 1
  95. }
  96. }
  97. export default locale
  98. ```
  99. ### localize
  100. Put this object in `_lib/localize/index.js` inside your locale directory.
  101. Contains a number of functions for used by `format`:
  102. ```js
  103. var localize = {
  104. ordinalNumber,
  105. era,
  106. quarter,
  107. month,
  108. day,
  109. dayPeriod
  110. }
  111. export default localize
  112. ```
  113. #### localize.ordinalNumber
  114. Function that takes a numeric argument and returns a string with ordinal number:
  115. ```js
  116. // In `en-US` locale:
  117. function ordinalNumber (dirtyNumber, dirtyOptions) {
  118. var number = Number(dirtyNumber)
  119. var rem100 = number % 100
  120. if (rem100 > 20 || rem100 < 10) {
  121. switch (rem100 % 10) {
  122. case 1:
  123. return number + 'st'
  124. case 2:
  125. return number + 'nd'
  126. case 3:
  127. return number + 'rd'
  128. }
  129. }
  130. return number + 'th'
  131. }
  132. var localize = {
  133. ordinalNumber: ordinalNumber,
  134. // ...
  135. }
  136. ```
  137. If the form of the ordinal number depends on the grammatical case (or other grammatical structures),
  138. use `options.unit` argument which could be one of the values 'year', 'quarter', 'month', 'week',
  139. 'date', 'dayOfYear', 'day', 'hour', 'minute' or 'second':
  140. ```js
  141. // In `ru` locale:
  142. function ordinalNumber (dirtyNumber, dirtyOptions) {
  143. var options = dirtyOptions || {}
  144. var unit = String(options.unit)
  145. var suffix
  146. if (unit === 'date') {
  147. suffix = '-е'
  148. } else if (unit === 'week' || unit === 'minute' || unit === 'second') {
  149. suffix = '-я'
  150. } else {
  151. suffix = '-й'
  152. }
  153. return dirtyNumber + suffix
  154. }
  155. ```
  156. #### localize.era and using buildLocalizeFn function
  157. Localizes a numeric era. Takes either 0 or 1 as the first argument.
  158. As with many of the `localize` functions, they can be generated by built-in
  159. `buildLocalizeFn` function.
  160. From CLDR chart, use ['Date & Time'/'Gregorian'/'Eras'](https://www.unicode.org/cldr/charts/32/summary/en.html#1771) values.
  161. ```js
  162. // In `en-US` locale:
  163. import buildLocalizeFn from '../../../_lib/buildLocalizeFn/index.js'
  164. var eraValues = {
  165. narrow: ['B', 'A'],
  166. abbreviated: ['BC', 'AD'],
  167. wide: ['Before Christ', 'Anno Domini']
  168. }
  169. var localize = {
  170. // ...
  171. era: buildLocalizeFn({
  172. values: eraValues,
  173. defaultWidth: 'wide'
  174. }),
  175. // ...
  176. }
  177. export default localize
  178. ```
  179. General usage of the function:
  180. ```js
  181. var result = locale.localize.era(1, {width: 'abbreviated'})
  182. //=> 'AD'
  183. ```
  184. If `width` is not provided or the `values` object does not contain values for the provided width,
  185. `defaultWidth` will be used. `defaultWidth` should indicate the longest form of the localized value.
  186. The same is true for all other `localize` functions.
  187. `width` for `localize.era` function could be either 'narrow', 'abbreviated' or 'wide'.
  188. ```js
  189. var result = locale.localize.era(1, {width: 'foobar'})
  190. //=> 'Anno Domini'
  191. ```
  192. #### Formatting localizers
  193. For some languages, there is a difference for "stand-alone" localizers and "formatting" localizers.
  194. "Stand-alone" means that the resulting value should make grammatical sense without context.
  195. "Formatting" means that the resulting value should be declined using the grammar rules of the language
  196. as if the value was a part of a date.
  197. For example, for languages with grammatical cases, stand-alone month could be in the nominative case ("January"),
  198. and formatting month could decline as a part of phrase "1st of January".
  199. In this case, use parameters `formattingValues` and `defaultFormattingWidth` of `buildLocalizeFn` function.
  200. Any localizer could be stand-alone and formatting.
  201. Check the CLDR chart for the unit to see if stand-alone and formatting values are different for a certain unit.
  202. If there's no difference (usually it happens in languages without grammatical cases),
  203. parameters `formattingValues` and `defaultFormattingWidth` are not needed.
  204. In this example, in Russian language a stand-alone month is in the nominative case ("январь"),
  205. and formatting month is in the genitive case ("января" as in "1-е января"). Notice the different endings:
  206. ```js
  207. // In `ru` locale:
  208. var monthValues = {
  209. narrow: ['Я', 'Ф', 'М', 'А', 'М', 'И', 'И', 'А', 'С', 'О', 'Н', 'Д'],
  210. abbreviated: ['янв.', 'фев.', 'март', 'апр.', 'май', 'июнь', 'июль', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.'],
  211. wide: ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь']
  212. }
  213. var formattingMonthValues = {
  214. narrow: ['Я', 'Ф', 'М', 'А', 'М', 'И', 'И', 'А', 'С', 'О', 'Н', 'Д'],
  215. abbreviated: ['янв.', 'фев.', 'мар.', 'апр.', 'мая', 'июн.', 'июл.', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.'],
  216. wide: ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
  217. }
  218. var localize = {
  219. // ...
  220. month: buildLocalizeFn({
  221. values: monthValues,
  222. defaultWidth: 'wide',
  223. formattingValues: formattingMonthValues,
  224. defaultFormattingWidth: 'wide'
  225. }),
  226. // ...
  227. }
  228. export default localize
  229. ```
  230. #### localize.quarter
  231. Localizes a quarter. Takes 1, 2, 3 or 4 as the first argument.
  232. `width` could be either 'narrow', 'abbreviated' or 'wide'.
  233. From CLDR chart, use ['Date & Time'/'Gregorian'/'Quarters'](https://www.unicode.org/cldr/charts/32/summary/en.html#1781) values.
  234. ```js
  235. // In `en-US` locale:
  236. import buildLocalizeFn from '../../../_lib/buildLocalizeFn/index.js'
  237. var quarterValues = {
  238. narrow: ['1', '2', '3', '4'],
  239. abbreviated: ['Q1', 'Q2', 'Q3', 'Q4'],
  240. wide: ['1st quarter', '2nd quarter', '3rd quarter', '4th quarter']
  241. }
  242. var localize = {
  243. // ...
  244. quarter: buildLocalizeFn({
  245. values: quarterValues,
  246. defaultWidth: 'wide',
  247. argumentCallback: function (quarter) {
  248. return Number(quarter) - 1
  249. }
  250. }),
  251. // ...
  252. }
  253. export default localize
  254. ```
  255. Note the usage of `argumentCallback` here. It converts the value passed into `localize.quarter` function
  256. (one of 1, 2, 3 or 4) into the index of the values array inside `quarterValues` (one of 0, 1, 2 or 3).
  257. #### localize.month
  258. Localizes a month. Takes numbers between 0 (for January) and 11 (for December).
  259. `width` could be either 'narrow', 'abbreviated' or 'wide'.
  260. From CLDR chart, use ['Date & Time'/'Gregorian'/'Months'](https://www.unicode.org/cldr/charts/32/summary/en.html#1793) values.
  261. ```js
  262. // In `en-US` locale:
  263. import buildLocalizeFn from '../../../_lib/buildLocalizeFn/index.js'
  264. var monthValues = {
  265. narrow: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'],
  266. abbreviated: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
  267. wide: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
  268. }
  269. var localize = {
  270. // ...
  271. month: buildLocalizeFn({
  272. values: monthValues,
  273. defaultWidth: 'wide'
  274. }),
  275. // ...
  276. }
  277. export default localize
  278. ```
  279. **NOTE**: in English, the names of days of the week and months are capitalized.
  280. Check if the same is true for the language you're working on.
  281. Generally, formatted dates should look like they are in the middle of a sentence,
  282. e.g. in Spanish language the weekdays and months should be in the lowercase:
  283. ```js
  284. // In `es` locale:
  285. var monthValues = {
  286. narrow: ['E', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'],
  287. abbreviated: ['ene.', 'feb.', 'mar.', 'abr.', 'may.', 'jun.', 'jul.', 'ago.', 'sep.', 'oct.', 'nov.', 'dic.'],
  288. wide: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre']
  289. }
  290. ```
  291. `monthValues.narrow` are usually capitalized in every language. Check the CLDR chart for your language.
  292. #### localize.day
  293. Localizes a week day. Takes numbers between 0 (for Sunday) and 6 (for Saturday).
  294. `width` could be either 'narrow', 'short', 'abbreviated' or 'wide'.
  295. From CLDR chart, use ['Date & Time'/'Gregorian'/'Days'](https://www.unicode.org/cldr/charts/32/summary/en.html#1829) values.
  296. ```js
  297. // In `en-US` locale:
  298. import buildLocalizeFn from '../../../_lib/buildLocalizeFn/index.js'
  299. var dayValues = {
  300. narrow: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
  301. short: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
  302. abbreviated: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
  303. wide: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
  304. }
  305. var localize = {
  306. // ...
  307. day: buildLocalizeFn({
  308. values: dayValues,
  309. defaultWidth: 'wide'
  310. }),
  311. // ...
  312. }
  313. export default localize
  314. ```
  315. **NOTE**: the rules of capitalization from `localize.month` are also true for `localize.day`.
  316. #### localize.dayPeriod
  317. Localizes a certain day period.
  318. Could take of these strings as the argument: 'am', 'pm', 'midnight', 'noon', 'morning', 'afternoon', 'evening', 'night'.
  319. `width` could be either 'narrow', 'abbreviated' or 'wide'.
  320. From CLDR chart, use ['Date & Time'/'Gregorian'/'Day periods'](https://www.unicode.org/cldr/charts/32/summary/en.html#1857) values.
  321. ```js
  322. // In `en-US` locale:
  323. import buildLocalizeFn from '../../../_lib/buildLocalizeFn/index.js'
  324. var dayPeriodValues = {
  325. narrow: {
  326. am: 'a',
  327. pm: 'p',
  328. midnight: 'mi',
  329. noon: 'n',
  330. morning: 'in the morning',
  331. afternoon: 'in the afternoon',
  332. evening: 'in the evening',
  333. night: 'at night'
  334. },
  335. abbreviated: {
  336. am: 'AM',
  337. pm: 'PM',
  338. midnight: 'midnight',
  339. noon: 'noon',
  340. morning: 'in the morning',
  341. afternoon: 'in the afternoon',
  342. evening: 'in the evening',
  343. night: 'at night'
  344. },
  345. wide: {
  346. am: 'a.m.',
  347. pm: 'p.m.',
  348. midnight: 'midnight',
  349. noon: 'noon',
  350. morning: 'in the morning',
  351. afternoon: 'in the afternoon',
  352. evening: 'in the evening',
  353. night: 'at night'
  354. }
  355. }
  356. var localize = {
  357. // ...
  358. dayPeriod: buildLocalizeFn({
  359. values: dayPeriodValues,
  360. defaultWidth: 'wide'
  361. })
  362. }
  363. export default localize
  364. ```
  365. ### formatLong
  366. Put this object in `_lib/formatLong/index.js` inside your locale directory.
  367. Locale date formats written in `format` token string format.
  368. See the list of tokens: https://date-fns.org/docs/format
  369. Use https://en.wikipedia.org/wiki/Date_format_by_country and CLDR chart as the reference.
  370. #### formatLong.dateFormats
  371. Use ['Date & Time'/'Gregorian'/'Formats - Standard - Date Formats'](https://www.unicode.org/cldr/charts/32/summary/en.html#1901) values
  372. from the CLDR chart as a reference.
  373. ```js
  374. // In `en-US` locale
  375. import buildFormatLongFn from '../../../_lib/buildFormatLongFn/index.js'
  376. var dateFormats = {
  377. full: 'EEEE, MMMM do, y',
  378. long: 'MMMM do, y',
  379. medium: 'MMM d, y',
  380. short: 'MM/dd/yyyy'
  381. }
  382. var formatLong = {
  383. date: buildFormatLongFn({
  384. formats: dateFormats,
  385. defaultWidth: 'full'
  386. }),
  387. // ...
  388. }
  389. export default formatLong
  390. ```
  391. `dateFormats.long` usually contains the longest form of writing the year, the month, and the day of the month.
  392. Use ordinal day of the month ('do' token) where applicable (date-fns, unlike CLDR supports ordinal numbers).
  393. `dateFormats.full` contains the same but with the day of the week.
  394. `dateFormats.medium` contains the same values as `dateFormats.long`, but with short form of month and non-ordinal day.
  395. `dateFormats.short` usually contains strictly numerical form of the date.
  396. Pay attention to the order of units (big-, little- or middle-endian)
  397. #### formatLong.timeFormats
  398. Use ['Date & Time'/'Gregorian'/'Formats - Standard - Time Formats'](https://www.unicode.org/cldr/charts/32/summary/en.html#1906) values
  399. from the CLDR chart as a reference.
  400. Use some variation of 'h:mm aa' for 12-hour clock locales or 'H:mm' for 24-hour clock locales. Use the local time separator.
  401. ```js
  402. // In `en-US` locale
  403. import buildFormatLongFn from '../../../_lib/buildFormatLongFn/index.js'
  404. var timeFormats = {
  405. full: 'h:mm:ss a zzzz',
  406. long: 'h:mm:ss a z',
  407. medium: 'h:mm:ss a',
  408. short: 'h:mm a'
  409. }
  410. var formatLong = {
  411. // ...
  412. time: buildFormatLongFn({
  413. formats: timeFormats,
  414. defaultWidth: 'full'
  415. }),
  416. // ...
  417. }
  418. export default formatLong
  419. ```
  420. #### formatLong.dateTimeFormats
  421. Use
  422. ['Date & Time'/'Gregorian'/'Formats - Standard - Date & Time Combination Formats'](https://www.unicode.org/cldr/charts/32/summary/en.html#1910)
  423. values from the CLDR chart.
  424. ```js
  425. // In `en-US` locale
  426. import buildFormatLongFn from '../../../_lib/buildFormatLongFn/index.js'
  427. var dateTimeFormats = {
  428. full: "{{date}} 'at' {{time}}",
  429. long: "{{date}} 'at' {{time}}",
  430. medium: '{{date}}, {{time}}',
  431. short: '{{date}}, {{time}}'
  432. }
  433. var formatLong = {
  434. // ...
  435. dateTime: buildFormatLongFn({
  436. formats: dateTimeFormats,
  437. defaultWidth: 'full'
  438. })
  439. }
  440. export default formatLong
  441. ```
  442. '{{date}}' and '{{time}}' from the strings will be replaced with the date and time respectively.
  443. ### formatRelative
  444. Put this function in `_lib/formatRelative/index.js` inside your locale directory.
  445. Relative date formats written in `format` token string format.
  446. See the list of tokens: https://date-fns.org/docs/format.
  447. Has to process `lastWeek`, `yesterday`, `today`, `tomorrow`, `nextWeek` and `other` tokens.
  448. ```javascript
  449. // In `en-US` locale
  450. var formatRelativeLocale = {
  451. lastWeek: "'last' eeee 'at' p",
  452. yesterday: "'yesterday at' p",
  453. today: "'today at' p",
  454. tomorrow: "'tomorrow at' p",
  455. nextWeek: "eeee 'at' p",
  456. other: 'P'
  457. }
  458. export default function formatRelative (token, date, baseDate, options) {
  459. return formatRelativeLocale[token]
  460. }
  461. ```
  462. You can use `date` and `baseDate` supplied to the function for the difficult situations
  463. (e.g. grammatical genders and cases of the days of the week)
  464. Both `date` and `baseDate` are converted to UTC timezone, which means
  465. that you should use UTC methods to take the date values (i.e. `date.getUTCDay()` instead of `date.getDay()`).
  466. You can use UTC functions from `src/_lib` in date-fns root directory if they are available.
  467. Don't forget to pass `options` object to them!
  468. Example is below. Note the different grammatical case for weekdays (accusative instead of nominative)
  469. and declension of word "прошлый" which depends on the grammatical gender of the weekday:
  470. ```javascript
  471. // In `ru` locale
  472. import isSameUTCWeek from '../../../../_lib/isSameUTCWeek/index.js'
  473. var accusativeWeekdays = ['воскресенье', 'понедельник', 'вторник', 'среду', 'четверг', 'пятницу', 'субботу']
  474. function lastWeek (day) {
  475. var weekday = accusativeWeekdays[day]
  476. switch (day) {
  477. case 0:
  478. return "'в прошлое " + weekday + " в' p"
  479. case 1:
  480. case 2:
  481. case 4:
  482. return "'в прошлый " + weekday + " в' p"
  483. case 3:
  484. case 5:
  485. case 6:
  486. return "'в прошлую " + weekday + " в' p"
  487. }
  488. }
  489. function thisWeek (day) {
  490. // ...
  491. }
  492. function nextWeek (day) {
  493. // ...
  494. }
  495. var formatRelativeLocale = {
  496. lastWeek: function (date, baseDate, options) {
  497. var day = date.getUTCDay()
  498. if (isSameUTCWeek(date, baseDate, options)) {
  499. return thisWeek(day)
  500. } else {
  501. return lastWeek(day)
  502. }
  503. },
  504. yesterday: "'вчера в' p",
  505. today: "'сегодня в' p",
  506. tomorrow: "'завтра в' p",
  507. nextWeek: function (date, baseDate, options) {
  508. var day = date.getUTCDay()
  509. if (isSameUTCWeek(date, baseDate, options)) {
  510. return thisWeek(day)
  511. } else {
  512. return nextWeek(day)
  513. }
  514. },
  515. other: 'P'
  516. }
  517. export default function formatRelative (token, date, baseDate, options) {
  518. var format = formatRelativeLocale[token]
  519. if (typeof format === 'function') {
  520. return format(date, baseDate, options)
  521. }
  522. return format
  523. }
  524. ```
  525. ### match
  526. Put this object in `_lib/match/index.js` inside your locale directory.
  527. Contains the functions used by `parse` to parse a localized value:
  528. ```js
  529. // In `en-US` locale:
  530. import buildMatchPatternFn from '../../../_lib/buildMatchPatternFn/index.js'
  531. import buildMatchFn from '../../../_lib/buildMatchFn/index.js'
  532. var matchOrdinalNumberPattern = /^(\d+)(th|st|nd|rd)?/i
  533. var parseOrdinalNumberPattern = /\d+/i
  534. var matchEraPatterns = {
  535. narrow: /^(b|a)/i,
  536. abbreviated: /^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,
  537. wide: /^(before christ|before common era|anno domini|common era)/i
  538. }
  539. var parseEraPatterns = {
  540. any: [/^b/i, /^(a|c)/i]
  541. }
  542. var matchQuarterPatterns = {
  543. narrow: /^[1234]/i,
  544. abbreviated: /^q[1234]/i,
  545. wide: /^[1234](th|st|nd|rd)? quarter/i
  546. }
  547. var parseQuarterPatterns = {
  548. any: [/1/i, /2/i, /3/i, /4/i]
  549. }
  550. var matchMonthPatterns = {
  551. narrow: /^[jfmasond]/i,
  552. abbreviated: /^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,
  553. wide: /^(january|february|march|april|may|june|july|august|september|october|november|december)/i
  554. }
  555. var parseMonthPatterns = {
  556. narrow: [/^j/i, /^f/i, /^m/i, /^a/i, /^m/i, /^j/i, /^j/i, /^a/i, /^s/i, /^o/i, /^n/i, /^d/i],
  557. any: [/^ja/i, /^f/i, /^mar/i, /^ap/i, /^may/i, /^jun/i, /^jul/i, /^au/i, /^s/i, /^o/i, /^n/i, /^d/i]
  558. }
  559. var matchDayPatterns = {
  560. narrow: /^[smtwf]/i,
  561. short: /^(su|mo|tu|we|th|fr|sa)/i,
  562. abbreviated: /^(sun|mon|tue|wed|thu|fri|sat)/i,
  563. wide: /^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i
  564. }
  565. var parseDayPatterns = {
  566. narrow: [/^s/i, /^m/i, /^t/i, /^w/i, /^t/i, /^f/i, /^s/i],
  567. any: [/^su/i, /^m/i, /^tu/i, /^w/i, /^th/i, /^f/i, /^sa/i]
  568. }
  569. var matchDayPeriodPatterns = {
  570. narrow: /^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,
  571. any: /^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i
  572. }
  573. var parseDayPeriodPatterns = {
  574. any: {
  575. am: /^a/i,
  576. pm: /^p/i,
  577. midnight: /^mi/i,
  578. noon: /^no/i,
  579. morning: /morning/i,
  580. afternoon: /afternoon/i,
  581. evening: /evening/i,
  582. night: /night/i
  583. }
  584. }
  585. var match = {
  586. ordinalNumber: buildMatchPatternFn({
  587. matchPattern: matchOrdinalNumberPattern,
  588. parsePattern: parseOrdinalNumberPattern,
  589. valueCallback: function (value) {
  590. return parseInt(value, 10)
  591. }
  592. }),
  593. era: buildMatchFn({
  594. matchPatterns: matchEraPatterns,
  595. defaultMatchWidth: 'wide',
  596. parsePatterns: parseEraPatterns,
  597. defaultParseWidth: 'any'
  598. }),
  599. quarter: buildMatchFn({
  600. matchPatterns: matchQuarterPatterns,
  601. defaultMatchWidth: 'wide',
  602. parsePatterns: parseQuarterPatterns,
  603. defaultParseWidth: 'any',
  604. valueCallback: function (index) {
  605. return index + 1
  606. }
  607. }),
  608. month: buildMatchFn({
  609. matchPatterns: matchMonthPatterns,
  610. defaultMatchWidth: 'wide',
  611. parsePatterns: parseMonthPatterns,
  612. defaultParseWidth: 'any'
  613. }),
  614. day: buildMatchFn({
  615. matchPatterns: matchDayPatterns,
  616. defaultMatchWidth: 'wide',
  617. parsePatterns: parseDayPatterns,
  618. defaultParseWidth: 'any'
  619. }),
  620. dayPeriod: buildMatchFn({
  621. matchPatterns: matchDayPeriodPatterns,
  622. defaultMatchWidth: 'any',
  623. parsePatterns: parseDayPeriodPatterns,
  624. defaultParseWidth: 'any'
  625. })
  626. }
  627. export default match
  628. ```
  629. These functions mirror those in `localize`.
  630. For `matchPatterns` the patterns should match the whole meaningful word for the parsed value
  631. (which will be cut from the string in the process of parsing).
  632. `parsePatterns` contains patterns to detect one of the values from the result of `matchPatterns`
  633. Note that the patterns for `parsePatterns` don't necessary contain the whole word:
  634. ```javascript
  635. // In `en-US` locale:
  636. var parseDayPatterns = {
  637. narrow: [/^s/i, /^m/i, /^t/i, /^w/i, /^t/i, /^f/i, /^s/i],
  638. any: [/^su/i, /^m/i, /^tu/i, /^w/i, /^th/i, /^f/i, /^sa/i]
  639. }
  640. ```
  641. but only the bare minimum to parse the value.
  642. Also note that all patterns have "case-insensitive" flags
  643. to match as much arbitrary user input as possible. For the same reason, try to match
  644. any variation of diacritical marks:
  645. ```javascript
  646. // In `eo` locale:
  647. var matchDayPatterns = {
  648. narrow: /^[dlmĵjvs]/i,
  649. short: /^(di|lu|ma|me|(ĵ|jx|jh|j)a|ve|sa)/i,
  650. abbreviated: /^(dim|lun|mar|mer|(ĵ|jx|jh|j)a(ŭ|ux|uh|u)|ven|sab)/i,
  651. wide: /^(diman(ĉ|cx|ch|c)o|lundo|mardo|merkredo|(ĵ|jx|jh|j)a(ŭ|ux|uh|u)do|vendredo|sabato)/i
  652. }
  653. var parseDayPatterns = {
  654. narrow: [/^d/i, /^l/i, /^m/i, /^m/i, /^(j|ĵ)/i, /^v/i, /^s/i],
  655. any: [/^d/i, /^l/i, /^ma/i, /^me/i, /^(j|ĵ)/i, /^v/i, /^s/i]
  656. }
  657. ```
  658. Here, for the word "dimanĉo" the functions will match also "dimancxo", "dimancho"
  659. and even grammatically incorrect "dimanco".
  660. Try to match any possible way of writing the word. Don't forget the grammatical cases:
  661. ```javascript
  662. // In `ru` locale:
  663. var matchMonthPatterns = {
  664. narrow: /^[яфмаисонд]/i,
  665. abbreviated: /^(янв|фев|март?|апр|ма[йя]|июн[ья]?|июл[ья]?|авг|сент?|окт|нояб?|дек)/i,
  666. wide: /^(январ[ья]|феврал[ья]|марта?|апрел[ья]|ма[йя]|июн[ья]|июл[ья]|августа?|сентябр[ья]|октябр[ья]|октябр[ья]|ноябр[ья]|декабр[ья])/i
  667. }
  668. ```
  669. and variations of short weekdays and months:
  670. ```javascript
  671. // In `ru` locale:
  672. var matchDayPatterns = {
  673. narrow: /^[впсч]/i,
  674. short: /^(вс|во|пн|по|вт|ср|чт|че|пт|пя|сб|су)\.?/i,
  675. abbreviated: /^(вск|вос|пнд|пон|втр|вто|срд|сре|чтв|чет|птн|пят|суб).?/i,
  676. wide: /^(воскресень[ея]|понедельника?|вторника?|сред[аы]|четверга?|пятниц[аы]|суббот[аы])/i
  677. }
  678. ```
  679. (here, the `abbreviated` pattern will match both `вск` and `вос` as the short of `воскресенье` {Sunday})
  680. In `match.ordinalNumber` match ordinal numbers as well as non-ordinal numbers:
  681. ```javascript
  682. // In `en-US` locale:
  683. var matchOrdinalNumberPattern = /^(\d+)(th|st|nd|rd)?/i
  684. ```
  685. Don't forget the grammatical genders:
  686. ```javascript
  687. // In `ru` locale:
  688. var matchOrdinalNumberPattern = /^(\d+)(-?(е|я|й|ое|ье|ая|ья|ый|ой|ий|ый))?/i
  689. ```
  690. ### formatDistance
  691. `formatDistance` property of locale is a function which takes three arguments:
  692. token passed by date-fns' `formatDistance` function (e.g. 'lessThanXMinutes'),
  693. a number of units to be displayed by the function
  694. (e.g. `locale.formatDistance('lessThanXMinutes', 5)` would display localized 'less than 5 minutes')
  695. and object with options.
  696. Your best guess is to copy `formatDistance` property from another locale and change the values.
  697. ### Tests
  698. To test locales we use snapshots. See [`en-US` snapshot](https://github.com/date-fns/date-fns/blob/master/src/locale/en-US/snapshot.md) for an example.
  699. To generate snapshots, run `yarn locale-snapshots`. The snapshot for the locale
  700. you're working on will appear in the root locale directory (e.g. `src/locales/ru/snapshot.md`).
  701. Once you are done with the locale, generate the snapshot and review the output values.
  702. ## Creating a locale with the same language as another locale
  703. Import the locale properties already implemented for the language,
  704. but replace unique properties.
  705. ```javascript
  706. // Same as en-US
  707. import formatDistance from '../en-US/_lib/formatDistance/index.js'
  708. import formatRelative from '../en-US/_lib/formatRelative/index.js'
  709. import localize from '../en-US/_lib/localize/index.js'
  710. import match from '../en-US/_lib/match/index.js'
  711. // Unique for en-GB
  712. import formatLong from './_lib/formatLong/index.js'
  713. /**
  714. * @type {Locale}
  715. * @category Locales
  716. * @summary English locale (United Kingdom).
  717. * @language English
  718. * @iso-639-2 eng
  719. * @author John Doe [@example]{@link https://github.com/example}
  720. */
  721. var locale = {
  722. formatDistance: formatDistance,
  723. formatLong: formatLong,
  724. formatRelative: formatRelative,
  725. localize: localize,
  726. match: match,
  727. // Unique for en-GB
  728. options: {
  729. weekStartsOn: 1,
  730. firstWeekContainsDate: 4
  731. }
  732. }
  733. export default locale
  734. ```