├── .npmignore ├── flow-typed.config.json ├── examples └── simple │ ├── index.html │ └── index.js ├── .babelrc.js ├── CONTRIBUTORS.ms ├── .flowconfig ├── src ├── types.js ├── language-strings │ ├── tr.js │ ├── fr-short.js │ ├── az-short.js │ ├── en-short.js │ ├── es-short.js │ ├── it-short.js │ ├── pt-short.js │ ├── tr-short.js │ ├── de-short.js │ ├── it.js │ ├── ka-short.js │ ├── pt-br-short.js │ ├── az.js │ ├── en-fuzzy-short.js │ ├── hy.js │ ├── ja.js │ ├── ro.js │ ├── zh-CN.js │ ├── bg.js │ ├── mk.js │ ├── gl.js │ ├── zh-TW.js │ ├── es.js │ ├── lt.js │ ├── lv.js │ ├── pt-br.js │ ├── sk.js │ ├── ko.js │ ├── pt.js │ ├── uz.js │ ├── da.js │ ├── eu.js │ ├── is.js │ ├── so.js │ ├── no.js │ ├── af.js │ ├── ca.js │ ├── cy.js │ ├── rw.js │ ├── oc.js │ ├── de.js │ ├── el.js │ ├── hi.js │ ├── si.js │ ├── sv.js │ ├── fr.js │ ├── id.js │ ├── vi.js │ ├── en-fuzzy.js │ ├── th.js │ ├── en.js │ ├── hu.js │ ├── nl.js │ ├── jv.js │ ├── ka.js │ ├── ta.js │ ├── dv.js │ ├── he.js │ ├── pl.js │ ├── fi.js │ ├── et.js │ ├── ky.js │ ├── fa-short.js │ ├── be.js │ ├── uk.js │ ├── rs.js │ ├── sr.js │ ├── bs.js │ ├── hr.js │ ├── sl.js │ ├── fa.js │ ├── ru.js │ ├── cs.js │ └── ar.js ├── dateParser.js ├── defaultFormatter.js ├── formatters │ └── buildFormatter.js └── index.js ├── flow-typed ├── npm │ ├── string-natural-compare_v3.x.x.js │ ├── flow-enums-runtime_v0.x.x.js │ ├── yargs_v17.x.x.js │ └── jest_v29.x.x.js ├── intl.js └── cssom.js ├── rollup.config.mjs ├── .gitignore ├── .prettierignore ├── __tests__ ├── formatter-value-tests.js └── index.js ├── .eslintrc ├── LICENSE ├── .github └── workflows │ └── build-and-deploy.yml ├── CHANGELOG.md ├── package.json ├── scripts └── gen-types.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | coverage 3 | examples 4 | node_modules 5 | src 6 | .babelrc 7 | .eslintrc 8 | .flowignore 9 | .prettierignore -------------------------------------------------------------------------------- /flow-typed.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": ["node", "dom", "bom", "intl", "cssom", "indexeddb", "serviceworkers", "webassembly", "jsx"] 3 | } -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Just a test 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | process.env['ES6'] ? null : '@babel/env', 4 | '@babel/react', 5 | '@babel/flow', 6 | ].filter(Boolean), 7 | plugins: [['babel-plugin-syntax-hermes-parser', { flow: 'detect' }]], 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTORS.ms: -------------------------------------------------------------------------------- 1 | # Thanks 2 | 3 | [Luke Karrys](https://github.com/lukekarrys) 4 | [Ian Johnson](https://github.com/ninjaferret) 5 | [Benjamin Flesch](https://github.com/bf) 6 | [Jonny Buchanan](https://github.com/insin) 7 | [Andrew Gremmo](https://github.com/andrewjgremmo) 8 | [Denis Sokolov](https://github.com/denis-sokolov) 9 | [Paul Bach](https://github.com/psolbach) 10 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/lib/.* 3 | .*/es6/.* 4 | .*/coverage/.* 5 | .*/node_modules/module-deps/.* 6 | .*/malformed_package_json/.* 7 | 8 | [include] 9 | 10 | [libs] 11 | 12 | [options] 13 | emoji=true 14 | exact_by_default=true 15 | experimental.const_params=true 16 | module.use_strict=true 17 | munge_underscores=true 18 | suppress_type=$FlowFixMe 19 | suppress_type=$FlowTODO 20 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | 3 | export type Unit = 4 | | 'second' 5 | | 'minute' 6 | | 'hour' 7 | | 'day' 8 | | 'week' 9 | | 'month' 10 | | 'year' 11 | 12 | export type Suffix = 'ago' | 'from now' 13 | 14 | export type Formatter = ( 15 | value: number, 16 | unit: Unit, 17 | suffix: Suffix, 18 | epochMilliseconds: number, 19 | nextFormatter: Formatter, 20 | now: () => number, 21 | ) => React.Node -------------------------------------------------------------------------------- /flow-typed/npm/string-natural-compare_v3.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 232f932594725cf979b6f7524097aceb 2 | // flow-typed version: d8a68b7d56/string-natural-compare_v3.x.x/flow_>=v0.83.x 3 | 4 | declare module 'string-natural-compare' { 5 | declare function naturalCompare( 6 | a: string, 7 | b: string, 8 | options?: {| 9 | caseInsensitive?: boolean, 10 | alphabet?: string, 11 | |}, 12 | ): number; 13 | 14 | declare module.exports: typeof naturalCompare; 15 | } 16 | -------------------------------------------------------------------------------- /src/language-strings/tr.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Turkish 5 | const strings: L10nsStrings = { 6 | suffixAgo: 'önce', 7 | suffixFromNow: 'sonra', 8 | second: '1 saniye', 9 | seconds: '%d saniye', 10 | minute: '1 dakika', 11 | minutes: '%d dakika', 12 | hour: '1 saat', 13 | hours: '%d saat', 14 | day: '1 gün', 15 | days: '%d gün', 16 | month: '1 ay', 17 | months: '%d ay', 18 | year: '1 yıl', 19 | years: '%d yıl', 20 | } 21 | 22 | export default strings 23 | -------------------------------------------------------------------------------- /src/language-strings/fr-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // French shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'il y a', 7 | prefixFromNow: "d'ici", 8 | seconds: "moins d'une minute", 9 | minute: 'une minute', 10 | minutes: '%d minutes', 11 | hour: 'une heure', 12 | hours: '%d heures', 13 | day: 'un jour', 14 | days: '%d jours', 15 | month: 'un mois', 16 | months: '%d mois', 17 | year: 'un an', 18 | years: '%d ans', 19 | } 20 | 21 | export default strings 22 | -------------------------------------------------------------------------------- /src/language-strings/az-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const strings: L10nsStrings = { 5 | prefixAgo: null, 6 | prefixFromNow: null, 7 | suffixAgo: '', 8 | suffixFromNow: '', 9 | seconds: '1 dəq', 10 | minute: '1 dəq', 11 | minutes: '%d dəq', 12 | hour: '1 saat', 13 | hours: '%d saat', 14 | day: '1 gün', 15 | days: '%d gün', 16 | month: '1 ay', 17 | months: '%d ay', 18 | year: '1 il', 19 | years: '%d il', 20 | wordSeparator: '', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/en-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // English shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: '1m', 11 | minute: '1m', 12 | minutes: '%dm', 13 | hour: '1h', 14 | hours: '%dh', 15 | day: '1d', 16 | days: '%dd', 17 | month: '1mo', 18 | months: '%dmo', 19 | year: '1yr', 20 | years: '%dyr', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/es-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Spanish shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: '1m', 11 | minute: '1m', 12 | minutes: '%dm', 13 | hour: '1h', 14 | hours: '%dh', 15 | day: '1d', 16 | days: '%dd', 17 | month: '1me', 18 | months: '%dme', 19 | year: '1a', 20 | years: '%da', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/it-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Italian shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: '1m', 11 | minute: '1m', 12 | minutes: '%dm', 13 | hour: '1h', 14 | hours: '%dh', 15 | day: '1g', 16 | days: '%dg', 17 | month: '1me', 18 | months: '%dme', 19 | year: '1a', 20 | years: '%da', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/pt-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Portuguese shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: '1m', 11 | minute: '1m', 12 | minutes: '%dm', 13 | hour: '1h', 14 | hours: '%dh', 15 | day: '1d', 16 | days: '%dd', 17 | month: '1M', 18 | months: '%dM', 19 | year: '1a', 20 | years: '%da', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/tr-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Turkish shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: '1sn', 11 | minute: '1d', 12 | minutes: '%dd', 13 | hour: '1s', 14 | hours: '%ds', 15 | day: '1g', 16 | days: '%dg', 17 | month: '1ay', 18 | months: '%day', 19 | year: '1y', 20 | years: '%dy', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/de-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // German shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: 's', 11 | minute: '1m', 12 | minutes: '%dm', 13 | hour: '1h', 14 | hours: '%dh', 15 | day: '1T.', 16 | days: '%dT.', 17 | month: '1Mt.', 18 | months: '%dMt.', 19 | year: '1J.', 20 | years: '%dJ.', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/it.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Italian 5 | const strings: L10nsStrings = { 6 | suffixAgo: 'fa', 7 | suffixFromNow: 'da ora', 8 | seconds: 'meno di un minuto', 9 | minute: 'circa un minuto', 10 | minutes: '%d minuti', 11 | hour: "circa un'ora", 12 | hours: 'circa %d ore', 13 | day: 'un giorno', 14 | days: '%d giorni', 15 | month: 'circa un mese', 16 | months: '%d mesi', 17 | year: 'circa un anno', 18 | years: '%d anni', 19 | } 20 | 21 | export default strings 22 | -------------------------------------------------------------------------------- /src/language-strings/ka-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // English shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: '1წმ', 11 | minute: '1წთ', 12 | minutes: '%dწთ', 13 | hour: '1სთ', 14 | hours: '%dსთ', 15 | day: '1დღე', 16 | days: '%dდღე', 17 | month: '1თვე', 18 | months: '%dთვე', 19 | year: '1წ', 20 | years: '%dწ', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/pt-br-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Portuguese Brasil shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: '1m', 11 | minute: '1m', 12 | minutes: '%dm', 13 | hour: '1h', 14 | hours: '%dh', 15 | day: '1d', 16 | days: '%dd', 17 | month: '1M', 18 | months: '%dM', 19 | year: '1a', 20 | years: '%da', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/az.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const strings: L10nsStrings = { 5 | prefixAgo: null, 6 | prefixFromNow: null, 7 | suffixAgo: 'əvvəl', 8 | suffixFromNow: 'sonra', 9 | seconds: 'saniyələr', 10 | minute: '1 dəqiqə', 11 | minutes: '%d dəqiqə', 12 | hour: '1 saat', 13 | hours: '%d saat', 14 | day: '1 gün', 15 | days: '%d gün', 16 | month: '1 ay', 17 | months: '%d ay', 18 | year: '1 il', 19 | years: '%d il', 20 | wordSeparator: '', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/en-fuzzy-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // English shortened 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: 'just now', 11 | minute: '1m', 12 | minutes: '%dm', 13 | hour: '1h', 14 | hours: '%dh', 15 | day: '1d', 16 | days: '%dd', 17 | month: '1mo', 18 | months: '%dmo', 19 | year: '1yr', 20 | years: '%dyr', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/hy.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Armenian 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'առաջ', 9 | suffixFromNow: 'հետո', 10 | seconds: 'վայրկյաններ', 11 | minute: 'մեկ րոպե', 12 | minutes: '%d րոպե', 13 | hour: 'մեկ ժամ', 14 | hours: '%d ժամ', 15 | day: 'մեկ օր', 16 | days: '%d օր', 17 | month: 'մեկ ամիս', 18 | months: '%d ամիս', 19 | year: 'մեկ տարի', 20 | years: '%d տարի', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/ja.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Japanese 5 | const strings: L10nsStrings = { 6 | prefixAgo: '', 7 | prefixFromNow: '今から', 8 | suffixAgo: '前', 9 | suffixFromNow: '後', 10 | seconds: '1 分未満', 11 | minute: '約 1 分', 12 | minutes: '%d 分', 13 | hour: '約 1 時間', 14 | hours: '約 %d 時間', 15 | day: '約 1 日', 16 | days: '約 %d 日', 17 | month: '約 1 ヶ月', 18 | months: '約 %d ヶ月', 19 | year: '約 1 年', 20 | years: '約 %d 年', 21 | wordSeparator: '', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/ro.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Romanian 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'acum', 7 | prefixFromNow: 'in timp de', 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: 'mai putin de un minut', 11 | minute: 'un minut', 12 | minutes: '%d minute', 13 | hour: 'o ora', 14 | hours: '%d ore', 15 | day: 'o zi', 16 | days: '%d zile', 17 | month: 'o luna', 18 | months: '%d luni', 19 | year: 'un an', 20 | years: '%d ani', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/zh-CN.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Simplified Chinese 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '之前', 9 | suffixFromNow: '之后', 10 | seconds: '不到1分钟', 11 | minute: '大约1分钟', 12 | minutes: '%d分钟', 13 | hour: '大约1小时', 14 | hours: '大约%d小时', 15 | day: '1天', 16 | days: '%d天', 17 | month: '大约1个月', 18 | months: '%d月', 19 | year: '大约1年', 20 | years: '%d年', 21 | 22 | wordSeparator: '', 23 | } 24 | 25 | export default strings 26 | -------------------------------------------------------------------------------- /src/language-strings/bg.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const strings: L10nsStrings = { 5 | prefixAgo: 'преди', 6 | prefixFromNow: 'след', 7 | suffixAgo: null, 8 | suffixFromNow: null, 9 | seconds: 'по-малко от минута', 10 | minute: 'една минута', 11 | minutes: '%d минути', 12 | hour: 'един час', 13 | hours: '%d часа', 14 | day: 'един ден', 15 | days: '%d дни', 16 | month: 'един месец', 17 | months: '%d месеца', 18 | year: 'една година', 19 | years: '%d години', 20 | } 21 | 22 | export default strings 23 | -------------------------------------------------------------------------------- /src/language-strings/mk.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Macedonian 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'пред', 7 | prefixFromNow: 'за', 8 | suffixAgo: null, 9 | suffixFromNow: null, 10 | seconds: '%d секунди', 11 | minute: '%d минута', 12 | minutes: '%d минути', 13 | hour: '%d час', 14 | hours: '%d часа', 15 | day: '%d ден', 16 | days: '%d денови', 17 | month: '%d месец', 18 | months: '%d месеци', 19 | year: '%d година', 20 | years: '%d години', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/gl.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Galician 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'hai', 7 | prefixFromNow: 'dentro de', 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: 'menos dun minuto', 11 | minute: 'un minuto', 12 | minutes: 'uns %d minutos', 13 | hour: 'unha hora', 14 | hours: '%d horas', 15 | day: 'un día', 16 | days: '%d días', 17 | month: 'un mes', 18 | months: '%d meses', 19 | year: 'un ano', 20 | years: '%d anos', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/zh-TW.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Traditional Chinese, zh-tw 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '之前', 9 | suffixFromNow: '之後', 10 | seconds: '不到1分鐘', 11 | minute: '大約1分鐘', 12 | minutes: '%d分鐘', 13 | hour: '大約1小時', 14 | hours: '%d小時', 15 | day: '大約1天', 16 | days: '%d天', 17 | month: '大約1個月', 18 | months: '%d個月', 19 | year: '大約1年', 20 | years: '%d年', 21 | 22 | wordSeparator: '', 23 | } 24 | 25 | export default strings 26 | -------------------------------------------------------------------------------- /src/language-strings/es.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Spanish 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'hace', 7 | prefixFromNow: 'dentro de', 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: 'menos de un minuto', 11 | minute: 'un minuto', 12 | minutes: 'unos %d minutos', 13 | hour: 'una hora', 14 | hours: '%d horas', 15 | day: 'un día', 16 | days: '%d días', 17 | month: 'un mes', 18 | months: '%d meses', 19 | year: 'un año', 20 | years: '%d años', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/lt.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Lithuanian 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'prieš', 7 | prefixFromNow: null, 8 | suffixAgo: null, 9 | suffixFromNow: 'nuo dabar', 10 | seconds: '%d sek.', 11 | minute: 'min.', 12 | minutes: '%d min.', 13 | hour: 'val.', 14 | hours: '%d val.', 15 | day: '1 d.', 16 | days: '%d d.', 17 | month: 'mėn.', 18 | months: '%d mėn.', 19 | year: 'metus', 20 | years: '%d metus', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/lv.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Latvian 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'pirms', 7 | prefixFromNow: null, 8 | suffixAgo: null, 9 | suffixFromNow: 'no šī brīža', 10 | seconds: '%d sek.', 11 | minute: 'min.', 12 | minutes: '%d min.', 13 | hour: 'st.', 14 | hours: '%d st.', 15 | day: '1 d.', 16 | days: '%d d.', 17 | month: 'mēnesis.', 18 | months: '%d mēnesis.', 19 | year: 'gads', 20 | years: '%d gads', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/pt-br.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Brazilian Portuguese 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'há', 7 | prefixFromNow: 'em', 8 | suffixAgo: null, 9 | suffixFromNow: null, 10 | seconds: 'alguns segundos', 11 | minute: 'um minuto', 12 | minutes: '%d minutos', 13 | hour: 'uma hora', 14 | hours: '%d horas', 15 | day: 'um dia', 16 | days: '%d dias', 17 | month: 'um mês', 18 | months: '%d meses', 19 | year: 'um ano', 20 | years: '%d anos', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/sk.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Slovak 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'pred', 7 | prefixFromNow: null, 8 | suffixAgo: null, 9 | suffixFromNow: null, 10 | seconds: 'menej než minútou', 11 | minute: 'minútou', 12 | minutes: '%d minútami', 13 | hour: 'hodinou', 14 | hours: '%d hodinami', 15 | day: '1 dňom', 16 | days: '%d dňami', 17 | month: '1 mesiacom', 18 | months: '%d mesiacmi', 19 | year: '1 rokom', 20 | years: '%d rokmi', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/ko.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Korean 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: '전', 9 | suffixFromNow: '후', 10 | seconds: '몇 초', 11 | minute: '약 1분', 12 | minutes: '%d분', 13 | hour: '약 1시간', 14 | hours: '약 %d시간', 15 | day: '하루', 16 | days: '%d일', 17 | week: '약 1주', 18 | weeks: '%d주', 19 | month: '약 1개월', 20 | months: '%d개월', 21 | year: '약 1년', 22 | years: '%d년', 23 | wordSeparator: ' ', 24 | } 25 | 26 | export default strings 27 | -------------------------------------------------------------------------------- /src/language-strings/pt.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Portuguese 5 | const strings: L10nsStrings = { 6 | suffixAgo: 'atrás', 7 | suffixFromNow: 'a partir de agora', 8 | seconds: 'menos de um minuto', 9 | minute: 'cerca de um minuto', 10 | minutes: '%d minutos', 11 | hour: 'cerca de uma hora', 12 | hours: 'cerca de %d horas', 13 | day: 'um dia', 14 | days: '%d dias', 15 | month: 'cerca de um mês', 16 | months: '%d meses', 17 | year: 'cerca de um ano', 18 | years: '%d anos', 19 | } 20 | 21 | export default strings 22 | -------------------------------------------------------------------------------- /src/language-strings/uz.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Uzbek 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: 'keyin', 8 | suffixAgo: 'avval', 9 | suffixFromNow: null, 10 | seconds: 'bir necha soniya', 11 | minute: '1 daqiqa', 12 | minutes: '%d daqiqa', 13 | hour: '1 soat', 14 | hours: '%d soat', 15 | day: '1 kun', 16 | days: '%d kun', 17 | month: '1 oy', 18 | months: '%d oy', 19 | year: '1 yil', 20 | years: '%d yil', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/da.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Danish 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'for', 7 | prefixFromNow: 'om', 8 | suffixAgo: 'siden', 9 | suffixFromNow: '', 10 | seconds: 'mindre end et minut', 11 | minute: 'ca. et minut', 12 | minutes: '%d minutter', 13 | hour: 'ca. en time', 14 | hours: 'ca. %d timer', 15 | day: 'en dag', 16 | days: '%d dage', 17 | month: 'ca. en måned', 18 | months: '%d måneder', 19 | year: 'ca. et år', 20 | years: '%d år', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/eu.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const strings: L10nsStrings = { 5 | prefixAgo: 'duela', 6 | prefixFromNow: 'hemendik', 7 | suffixAgo: '', 8 | suffixFromNow: 'barru', 9 | seconds: 'minutu bat bainu gutxiago', 10 | minute: 'minutu bat', 11 | minutes: '%d minutu inguru', 12 | hour: 'ordu bat', 13 | hours: '%d ordu', 14 | day: 'egun bat', 15 | days: '%d egun', 16 | month: 'hilabete bat', 17 | months: '%d hilabete', 18 | year: 'urte bat', 19 | years: '%d urte', 20 | } 21 | 22 | export default strings 23 | -------------------------------------------------------------------------------- /src/language-strings/is.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const strings: L10nsStrings = { 5 | prefixAgo: 'fyrir', 6 | prefixFromNow: 'eftir', 7 | suffixAgo: 'síðan', 8 | suffixFromNow: null, 9 | seconds: 'minna en mínútu', 10 | minute: 'mínútu', 11 | minutes: '%d mínútum', 12 | hour: 'klukkutíma', 13 | hours: 'um %d klukkutímum', 14 | day: 'degi', 15 | days: '%d dögum', 16 | month: 'mánuði', 17 | months: '%d mánuðum', 18 | year: 'ári', 19 | years: '%d árum', 20 | wordSeparator: ' ', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/so.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Somali 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'kahor', 9 | suffixFromNow: 'kadib', 10 | seconds: 'sikino', 11 | minute: 'daqiiqad', 12 | minutes: '%d daqiiqo', 13 | hour: 'saac', 14 | hours: '%d saacadood', 15 | day: 'maalin', 16 | days: '%d maalmood', 17 | month: 'bil', 18 | months: '%d bilood', 19 | year: 'sano', 20 | years: '%d sano', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/no.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Norwegian 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'for', 7 | prefixFromNow: 'om', 8 | suffixAgo: 'siden', 9 | suffixFromNow: '', 10 | seconds: 'mindre enn et minutt', 11 | minute: 'ca. et minutt', 12 | minutes: '%d minutter', 13 | hour: 'ca. en time', 14 | hours: 'ca. %d timer', 15 | day: 'en dag', 16 | days: '%d dager', 17 | month: 'ca. en måned', 18 | months: '%d måneder', 19 | year: 'ca. et år', 20 | years: '%d år', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/dateParser.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export default function dateParser(date: string | number | Date): Date { 4 | const parsed = new Date(date) 5 | if (!Number.isNaN(parsed.valueOf())) { 6 | return parsed 7 | } 8 | 9 | const parts: ?$ReadOnlyArray = String(date).match(/\d+/g) 10 | if (parts == null || parts.length <= 2) { 11 | return parsed 12 | } else { 13 | const [firstP, secondP, ...restPs] = parts.map((x) => parseInt(x)) 14 | const correctedParts = [firstP, secondP - 1, ...restPs] 15 | const isoDate = new Date(Date.UTC(...correctedParts)) 16 | return isoDate 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/language-strings/af.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const strings: L10nsStrings = { 5 | prefixAgo: null, 6 | prefixFromNow: null, 7 | suffixAgo: 'gelede', 8 | suffixFromNow: 'van nou af', 9 | second: '1 sekonde', 10 | seconds: '%d sekondes', 11 | minute: '1 minuut', 12 | minutes: '%d minute', 13 | hour: '1 uur', 14 | hours: '%d ure', 15 | day: '1 dag', 16 | days: '%d dae', 17 | month: '1 maand', 18 | months: '%d maande', 19 | year: '1 jaar', 20 | years: '%d jaar', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/ca.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Catalan 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'fa', 7 | prefixFromNow: "d'aqui a", 8 | suffixAgo: null, 9 | suffixFromNow: null, 10 | seconds: "menys d'1 minut", 11 | minute: '1 minut', 12 | minutes: 'uns %d minuts', 13 | hour: '1 hora', 14 | hours: 'unes %d hores', 15 | day: '1 dia', 16 | days: '%d dies', 17 | month: 'aproximadament un mes', 18 | months: '%d mesos', 19 | year: 'aproximadament un any', 20 | years: '%d anys', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/cy.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Welsh 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'yn ôl', 9 | suffixFromNow: 'o hyn', 10 | seconds: 'llai na munud', 11 | minute: 'am funud', 12 | minutes: '%d munud', 13 | hour: 'tua awr', 14 | hours: 'am %d awr', 15 | day: 'y dydd', 16 | days: '%d diwrnod', 17 | month: 'tua mis', 18 | months: '%d mis', 19 | year: 'am y flwyddyn', 20 | years: '%d blynedd', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/rw.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Kinyarwanda 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'hashize', 7 | prefixFromNow: 'mu', 8 | suffixAgo: null, 9 | suffixFromNow: null, 10 | seconds: 'amasegonda macye', 11 | minute: 'umunota', 12 | minutes: 'iminota %d', 13 | hour: 'isaha', 14 | hours: 'amasaha %d', 15 | day: 'umunsi', 16 | days: 'iminsi %d', 17 | month: 'ukwezi', 18 | months: 'amezi %d', 19 | year: 'umwaka', 20 | years: 'imyaka %d', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/oc.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Catalan 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'fa', 7 | prefixFromNow: "d’aqui", 8 | suffixAgo: null, 9 | suffixFromNow: null, 10 | seconds: "mens d'1 minuta", 11 | minute: '1 minuta', 12 | minutes: 'unas %d minutas', 13 | hour: '1 ora', 14 | hours: 'unas %d oras', 15 | day: '1 jorn', 16 | days: '%d jorns', 17 | month: 'aproximativament un mes', 18 | months: '%d meses', 19 | year: 'aproximativament un an', 20 | years: '%d ans', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/de.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // German 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'vor', 7 | prefixFromNow: 'in', 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: 'wenigen Sekunden', 11 | minute: 'etwa einer Minute', 12 | minutes: '%d Minuten', 13 | hour: 'etwa einer Stunde', 14 | hours: '%d Stunden', 15 | day: 'etwa einem Tag', 16 | days: '%d Tagen', 17 | month: 'etwa einem Monat', 18 | months: '%d Monaten', 19 | year: 'etwa einem Jahr', 20 | years: '%d Jahren', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/el.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Greek 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'πριν', 7 | prefixFromNow: 'σε', 8 | suffixAgo: '', 9 | suffixFromNow: '', 10 | seconds: 'λιγότερο από ένα λεπτό', 11 | minute: 'περίπου ένα λεπτό', 12 | minutes: '%d λεπτά', 13 | hour: 'περίπου μία ώρα', 14 | hours: 'περίπου %d ώρες', 15 | day: 'μία μέρα', 16 | days: '%d μέρες', 17 | month: 'περίπου ένα μήνα', 18 | months: '%d μήνες', 19 | year: 'περίπου ένα χρόνο', 20 | years: '%d χρόνια', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/hi.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Hindi (Template) 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: 'अब से', 8 | suffixAgo: 'पहले', 9 | suffixFromNow: 'मेे', 10 | seconds: 'एक मिनट से कम', 11 | minute: 'मिनट', 12 | minutes: '%d मिनट', 13 | hour: 'करीब एक घंटा', 14 | hours: 'करीब %d घंटे', 15 | day: 'दिन', 16 | days: '%d दिन', 17 | month: 'तक़रीबन एक महीना', 18 | months: '%d महीने', 19 | year: 'लगभग एक साल', 20 | years: '%d साल', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/si.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Sinhalese (SI) 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'පෙර', 9 | suffixFromNow: 'පසුව', 10 | seconds: 'තත්පර කිහිපයකට', 11 | minute: 'මිනිත්තුවකට පමණ', 12 | minutes: 'මිනිත්තු %d කට', 13 | hour: 'පැයක් පමණ ', 14 | hours: 'පැය %d කට පමණ', 15 | day: 'දවසක ට', 16 | days: 'දවස් %d කට ', 17 | month: 'මාසයක් පමණ', 18 | months: 'මාස %d කට', 19 | year: 'වසරක් පමණ', 20 | years: 'වසරක් %d කට පමණ', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/sv.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Swedish 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'för', 7 | prefixFromNow: 'om', 8 | suffixAgo: 'sedan', 9 | suffixFromNow: '', 10 | seconds: 'mindre än en minut', 11 | minute: 'ungefär en minut', 12 | minutes: '%d minuter', 13 | hour: 'ungefär en timme', 14 | hours: 'ungefär %d timmar', 15 | day: 'en dag', 16 | days: '%d dagar', 17 | month: 'ungefär en månad', 18 | months: '%d månader', 19 | year: 'ungefär ett år', 20 | years: '%d år', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/fr.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // French 5 | const strings: L10nsStrings = { 6 | // environ ~= about, it's optional 7 | prefixAgo: 'il y a', 8 | prefixFromNow: "d'ici", 9 | seconds: "moins d'une minute", 10 | minute: 'environ une minute', 11 | minutes: 'environ %d minutes', 12 | hour: 'environ une heure', 13 | hours: 'environ %d heures', 14 | day: 'environ un jour', 15 | days: 'environ %d jours', 16 | month: 'environ un mois', 17 | months: 'environ %d mois', 18 | year: 'un an', 19 | years: '%d ans', 20 | } 21 | 22 | export default strings 23 | -------------------------------------------------------------------------------- /src/language-strings/id.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Indonesian 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'yang lalu', 9 | suffixFromNow: 'dari sekarang', 10 | seconds: 'kurang dari semenit', 11 | minute: 'sekitar satu menit', 12 | minutes: '%d menit', 13 | hour: 'sekitar sejam', 14 | hours: 'sekitar %d jam', 15 | day: 'sehari', 16 | days: '%d hari', 17 | month: 'sekitar sebulan', 18 | months: '%d bulan', 19 | year: 'sekitar setahun', 20 | years: '%d tahun', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/vi.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Vietnamese 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'cách đây', 7 | prefixFromNow: null, 8 | suffixAgo: null, 9 | suffixFromNow: 'sau', 10 | seconds: 'chưa đến 1 phút', 11 | minute: 'khoảng 1 phút', 12 | minutes: '%d phút', 13 | hour: 'khoảng 1 tiếng', 14 | hours: 'khoảng %d tiếng', 15 | day: '1 ngày', 16 | days: '%d ngày', 17 | month: 'khoảng 1 tháng', 18 | months: '%d tháng', 19 | year: 'khoảng 1 năm', 20 | years: '%d năm', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/en-fuzzy.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // English (Template) 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'ago', 9 | suffixFromNow: 'from now', 10 | seconds: 'a moment', 11 | minute: 'about a minute', 12 | minutes: '%d minutes', 13 | hour: 'about an hour', 14 | hours: 'about %d hours', 15 | day: 'a day', 16 | days: '%d days', 17 | month: 'about a month', 18 | months: '%d months', 19 | year: 'about a year', 20 | years: '%d years', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/th.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Thai 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'ที่แล้ว', 9 | suffixFromNow: 'จากตอนนี้', 10 | seconds: 'น้อยกว่าหนึ่งนาที', 11 | minute: 'ประมาณหนึ่งนาที', 12 | minutes: '%d นาที', 13 | hour: 'ประมาณหนึ่งชั่วโมง', 14 | hours: 'ประมาณ %d ชั่วโมง', 15 | day: 'หนึ่งวัน', 16 | days: '%d วัน', 17 | month: 'ประมาณหนึ่งเดือน', 18 | months: '%d เดือน', 19 | year: 'ประมาณหนึ่งปี', 20 | years: '%d ปี', 21 | wordSeparator: '', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/en.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // English (Template) 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'ago', 9 | suffixFromNow: 'from now', 10 | seconds: 'less than a minute', 11 | minute: 'about a minute', 12 | minutes: '%d minutes', 13 | hour: 'about an hour', 14 | hours: 'about %d hours', 15 | day: 'a day', 16 | days: '%d days', 17 | month: 'about a month', 18 | months: '%d months', 19 | year: 'about a year', 20 | years: '%d years', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/hu.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Hungarian 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: null, 9 | suffixFromNow: null, 10 | seconds: 'kevesebb mint egy perce', 11 | minute: 'körülbelül egy perce', 12 | minutes: '%d perce', 13 | hour: 'körülbelül egy órája', 14 | hours: 'körülbelül %d órája', 15 | day: 'körülbelül egy napja', 16 | days: '%d napja', 17 | month: 'körülbelül egy hónapja', 18 | months: '%d hónapja', 19 | year: 'körülbelül egy éve', 20 | years: '%d éve', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/nl.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Dutch 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: 'over', 8 | suffixAgo: 'geleden', 9 | suffixFromNow: null, 10 | seconds: 'minder dan een minuut', 11 | minute: 'ongeveer een minuut', 12 | minutes: '%d minuten', 13 | hour: 'ongeveer een uur', 14 | hours: 'ongeveer %d uur', 15 | day: 'een dag', 16 | days: '%d dagen', 17 | month: 'ongeveer een maand', 18 | months: '%d maanden', 19 | year: 'ongeveer een jaar', 20 | years: '%d jaar', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/jv.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Javanesse (Boso Jowo) 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'kepungkur', 9 | suffixFromNow: 'seko saiki', 10 | seconds: 'kurang seko sakmenit', 11 | minute: 'kurang luwih sakmenit', 12 | minutes: '%d menit', 13 | hour: 'kurang luwih sakjam', 14 | hours: 'kurang luwih %d jam', 15 | day: 'sedina', 16 | days: '%d dina', 17 | month: 'kurang luwih sewulan', 18 | months: '%d wulan', 19 | year: 'kurang luwih setahun', 20 | years: '%d tahun', 21 | } 22 | 23 | export default strings 24 | -------------------------------------------------------------------------------- /src/language-strings/ka.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // English (Template) 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'წინ', 9 | suffixFromNow: 'შემდეგ', 10 | seconds: 'რამიდენიმე წამის', 11 | minute: 'დაახლოებით ერთი წუთის', 12 | minutes: '%d წუთის', 13 | hour: 'დაახლოებით ერთი საათის', 14 | hours: '%d საათის', 15 | day: 'ერთი დღის', 16 | days: '%d დღის', 17 | month: 'დაახლოებით ერთი თვის', 18 | months: '%d თვის', 19 | year: 'დაახლოებით ერთი თვის', 20 | years: '%d წლის', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /src/language-strings/ta.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // English (Template) 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'முன்பு', 9 | suffixFromNow: 'இப்போது முதல்', 10 | seconds: 'ஒரு நிமிடத்திற்கும் குறைவாக', 11 | minute: 'ஒரு நிமிடம்', 12 | minutes: '%d நிமிடங்கள்', 13 | hour: 'சுமார் ஒரு மணிநேரம்', 14 | hours: 'சுமார் %d மணிநேரம் ', 15 | day: 'ஒரு நாள்', 16 | days: '%d நாட்கள்', 17 | month: 'சுமார் ஒரு மாதம்', 18 | months: '%d மாதங்கள்', 19 | year: 'சுமார் ஒரு வருடம்', 20 | years: '%d ஆண்டுகள்', 21 | wordSeparator: ' ', 22 | } 23 | 24 | export default strings 25 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import commonjs from '@rollup/plugin-commonjs' 3 | import { babel } from '@rollup/plugin-babel' 4 | import resolve from '@rollup/plugin-node-resolve' 5 | import replace from '@rollup/plugin-replace' 6 | 7 | export default { 8 | external: [], 9 | input: 'examples/simple/index.js', 10 | output: { 11 | file: 'examples/simple/bundle.js', 12 | format: 'iife', 13 | }, 14 | plugins: [ 15 | babel({ babelHelpers: 'bundled' }), 16 | commonjs(), 17 | resolve({ 18 | browser: true, 19 | dedupe: ['react', 'react-dom'], 20 | }), 21 | replace({ 22 | 'process.env.NODE_ENV': JSON.stringify('production'), 23 | }), 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /src/language-strings/dv.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | /** 5 | * Dhivehi time in Thaana for timeago.js 6 | **/ 7 | const strings: L10nsStrings = { 8 | prefixAgo: null, 9 | prefixFromNow: null, 10 | suffixAgo: 'ކުރިން', 11 | suffixFromNow: 'ފަހުން', 12 | seconds: 'ސިކުންތުކޮޅެއް', 13 | minute: 'މިނިޓެއްވަރު', 14 | minutes: '%d މިނިޓު', 15 | hour: 'ގަޑިއެއްވަރު', 16 | hours: 'ގާތްގަނޑަކަށް %d ގަޑިއިރު', 17 | day: 'އެއް ދުވަސް', 18 | days: 'މީގެ %d ދުވަސް', 19 | month: 'މަހެއްވަރު', 20 | months: 'މީގެ %d މަސް', 21 | year: 'އަހަރެއްވަރު', 22 | years: 'މީގެ %d އަހަރު', 23 | wordSeparator: ' ', 24 | } 25 | 26 | export default strings 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | 26 | node_modules 27 | lib 28 | es6 29 | examples/simple/bundle.js 30 | 31 | .DS_Store 32 | */.DS_Store 33 | -------------------------------------------------------------------------------- /flow-typed/npm/flow-enums-runtime_v0.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 619aca2b81b5b5d287785c449228cc8a 2 | // flow-typed version: ca06757594/flow-enums-runtime_v0.x.x/flow_>=v0.83.x 3 | 4 | declare module 'flow-enums-runtime' { 5 | declare type Members = { +[key: string]: string }; 6 | 7 | declare type EnumPrototype = {| 8 | isValid: (x: $Keys) => boolean, 9 | cast: (x: V) => V | void, 10 | members: () => Array<$Keys>, 11 | getName: (value: string) => string, 12 | |}; 13 | 14 | declare type Enum = {| 15 | (members?: T): {| ...EnumPrototype, ...T |}, 16 | Mirrored: (members: Array<$Keys>) => {| ...Members, ...EnumPrototype |}, 17 | |}; 18 | 19 | declare module.exports: Enum; 20 | } 21 | -------------------------------------------------------------------------------- /src/language-strings/he.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Hebrew 5 | const strings: L10nsStrings = { 6 | prefixAgo: 'לפני', 7 | prefixFromNow: 'עוד', 8 | seconds: 'פחות מדקה', 9 | minute: 'דקה', 10 | minutes: '%d דקות', 11 | hour: 'שעה', 12 | hours: function (number) { 13 | return number === 2 ? 'שעתיים' : '%d שעות' 14 | }, 15 | day: 'יום', 16 | days: function (number) { 17 | return number === 2 ? 'יומיים' : '%d ימים' 18 | }, 19 | month: 'חודש', 20 | months: function (number) { 21 | return number === 2 ? 'חודשיים' : '%d חודשים' 22 | }, 23 | year: 'שנה', 24 | years: function (number) { 25 | return number === 2 ? 'שנתיים' : '%d שנים' 26 | }, 27 | } 28 | 29 | export default strings 30 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | 26 | node_modules 27 | lib 28 | es6 29 | examples/simple/bundle.js 30 | 31 | .DS_Store 32 | */.DS_Store 33 | yarn-error.log 34 | 35 | # Files not in .gitignore 36 | .babelrc 37 | .circleci 38 | CHANGELOG.md 39 | examples/simple/index.html 40 | package.json 41 | README.md -------------------------------------------------------------------------------- /__tests__/formatter-value-tests.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | import { render, screen } from '@testing-library/react' 3 | import React from 'react' 4 | import TimeAgo from '../src' 5 | 6 | test('1 minute ago', () => { 7 | render() 8 | expect(screen.getByText('1 minute ago')).toBeInTheDocument() 9 | }) 10 | 11 | it('should handle null formatter gracefully', () => { 12 | render() 13 | expect(screen.getByText('1 minute ago')).toBeInTheDocument() 14 | }) 15 | 16 | it('should handle empty function formatter', () => { 17 | render( {}} />) 18 | expect(screen.getByText('2 hours ago')).toBeInTheDocument() 19 | }) 20 | 21 | it('should handle formatter throwing an error', () => { 22 | render( 23 | { 26 | throw new Error('Faulty formatter') 27 | }} 28 | />, 29 | ) 30 | expect(screen.getByText('2 minutes ago')).toBeInTheDocument() 31 | }) 32 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:ft-flow/recommended", 5 | "plugin:react-hooks/recommended", 6 | "prettier" 7 | ], 8 | "plugins": [ 9 | "ft-flow", 10 | "react-hooks" 11 | ], 12 | "parser": "hermes-eslint", 13 | "rules": { 14 | "curly": ["error", "multi-line"], 15 | "linebreak-style": ["error", "unix"], 16 | "max-len": [ 17 | "error", 18 | { 19 | "code": 80, 20 | "ignoreComments": true, 21 | "ignoreStrings": true, 22 | "ignoreTemplateLiterals": true 23 | } 24 | ], 25 | "new-cap": "off", 26 | "no-case-declarations": "error", 27 | "no-var": "error", 28 | "prefer-const": "error", 29 | "no-unused-vars": [ 30 | "error", 31 | { 32 | "varsIgnorePattern": "^_", 33 | "argsIgnorePattern": "^_" 34 | } 35 | ] 36 | }, 37 | "env": { 38 | "node": true, 39 | "es6": true 40 | }, 41 | "ignorePatterns": [ 42 | "__tests__", 43 | "node_modules", 44 | "coverage", 45 | "examples", 46 | "lib", 47 | "es6" 48 | ] 49 | } -------------------------------------------------------------------------------- /src/language-strings/pl.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Polish 5 | function numpf(n: number, s: string, t: string): string { 6 | // s - 2-4, 22-24, 32-34 ... 7 | // t - 5-21, 25-31, ... 8 | const n10 = n % 10 9 | if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 10 | return s 11 | } else { 12 | return t 13 | } 14 | } 15 | 16 | const strings: L10nsStrings = { 17 | prefixAgo: null, 18 | prefixFromNow: 'za', 19 | suffixAgo: 'temu', 20 | suffixFromNow: null, 21 | seconds: 'mniej niż minutę', 22 | minute: 'minutę', 23 | minutes: function (value) { 24 | return numpf(value, '%d minuty', '%d minut') 25 | }, 26 | hour: 'godzinę', 27 | hours: function (value) { 28 | return numpf(value, '%d godziny', '%d godzin') 29 | }, 30 | day: 'dzień', 31 | days: '%d dni', 32 | month: 'miesiąc', 33 | months: function (value) { 34 | return numpf(value, '%d miesiące', '%d miesięcy') 35 | }, 36 | year: 'rok', 37 | years: function (value) { 38 | return numpf(value, '%d lata', '%d lat') 39 | }, 40 | } 41 | 42 | export default strings 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Naman Goel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/language-strings/fi.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Finnish 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'sitten', 9 | suffixFromNow: 'tulevaisuudessa', 10 | seconds: 'alle minuutti', 11 | minute: 'minuutti', 12 | minutes: '%d minuuttia', 13 | hour: 'tunti', 14 | hours: '%d tuntia', 15 | day: 'päivä', 16 | days: '%d päivää', 17 | month: 'kuukausi', 18 | months: '%d kuukautta', 19 | year: 'vuosi', 20 | years: '%d vuotta', 21 | } 22 | 23 | export default strings 24 | 25 | // The above is not a great localization because one would usually 26 | // write "2 days ago" in Finnish as "2 päivää sitten", however 27 | // one would write "2 days into the future" as "2:n päivän päästä" 28 | // which cannot be achieved with localization support this simple. 29 | // This is because Finnish has word suffixes (attached directly 30 | // to the end of the word). The word "day" is "päivä" in Finnish. 31 | // As workaround, the above localizations will say 32 | // "2 päivää tulevaisuudessa" which is understandable but 33 | // not as fluent. 34 | -------------------------------------------------------------------------------- /src/defaultFormatter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { Formatter, Suffix, Unit } from './types' 4 | 5 | const defaultFormatter: Formatter = ( 6 | value: number, 7 | _unit: Unit, 8 | suffix: string, 9 | ): string => { 10 | const unit = value !== 1 ? _unit + 's' : _unit 11 | return value + ' ' + unit + ' ' + suffix 12 | } 13 | 14 | export default defaultFormatter 15 | 16 | export type IntlFormatterOptions = $ReadOnly<{ 17 | locale?: void | string, 18 | localeMatcher?: 'lookup' | 'best fit', 19 | numberingSystem?: Intl$NumberingSystem, 20 | style?: 'long' | 'short' | 'narrow', 21 | numeric?: 'always' | 'auto', 22 | }> 23 | 24 | export const makeIntlFormatter: (IntlFormatterOptions) => Formatter = 25 | ({ locale, ...options }) => 26 | (value, unit, suffix) => { 27 | const RelativeTimeFormat = Intl.RelativeTimeFormat 28 | if (!RelativeTimeFormat) { 29 | throw new Error('Intl.RelativeTimeFormat is not supported') 30 | } 31 | 32 | const rtf = new RelativeTimeFormat(locale ?? undefined, { 33 | style: 'long', 34 | numeric: 'auto', 35 | ...options, 36 | }) 37 | 38 | return rtf.format(suffix === 'ago' ? -value : value, unit) 39 | } 40 | -------------------------------------------------------------------------------- /src/language-strings/et.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Estonian 5 | const strings: L10nsStrings = { 6 | prefixAgo: null, 7 | prefixFromNow: null, 8 | suffixAgo: 'tagasi', 9 | suffixFromNow: 'pärast', 10 | seconds: function (n, d) { 11 | return d < 0 ? 'vähem kui minuti aja' : 'vähem kui minut aega' 12 | }, 13 | minute: function (n, d) { 14 | return d < 0 ? 'umbes minuti aja' : 'umbes minut aega' 15 | }, 16 | minutes: function (n, d) { 17 | return d < 0 ? '%d minuti' : '%d minutit' 18 | }, 19 | hour: function (n, d) { 20 | return d < 0 ? 'umbes tunni aja' : 'umbes tund aega' 21 | }, 22 | hours: function (n, d) { 23 | return d < 0 ? '%d tunni' : '%d tundi' 24 | }, 25 | day: function (n, d) { 26 | return d < 0 ? 'umbes päeva' : 'umbes päev' 27 | }, 28 | days: '%d päeva', 29 | month: function (n, d) { 30 | return d < 0 ? 'umbes kuu aja' : 'umbes kuu aega' 31 | }, 32 | months: function (n, d) { 33 | return d < 0 ? '%d kuu' : '%d kuud' 34 | }, 35 | year: function (n, d) { 36 | return d < 0 ? 'umbes aasta aja' : 'umbes aasta aega' 37 | }, 38 | years: function (n, d) { 39 | return d < 0 ? '%d aasta' : '%d aastat' 40 | }, 41 | } 42 | 43 | export default strings 44 | -------------------------------------------------------------------------------- /src/language-strings/ky.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Russian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | // f - 1, 21, 31, ... 7 | // s - 2-4, 22-24, 32-34 ... 8 | // t - 5-20, 25-30, ... 9 | const n10 = n % 10 10 | if (n10 === 1 && (n === 1 || n > 20)) { 11 | return f 12 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 13 | return s 14 | } else { 15 | return t 16 | } 17 | } 18 | 19 | const strings: L10nsStrings = { 20 | prefixAgo: null, 21 | prefixFromNow: 'через', 22 | suffixAgo: 'мурун', 23 | suffixFromNow: null, 24 | seconds: '1 минуттан аз', 25 | minute: 'минута', 26 | minutes: function (value) { 27 | return numpf(value, '%d минута', '%d минута', '%d минут') 28 | }, 29 | hour: 'саат', 30 | hours: function (value) { 31 | return numpf(value, '%d саат', '%d саат', '%d саат') 32 | }, 33 | day: 'күн', 34 | days: function (value) { 35 | return numpf(value, '%d күн', '%d күн', '%d күн') 36 | }, 37 | month: 'ай', 38 | months: function (value) { 39 | return numpf(value, '%d ай', '%d ай', '%d ай') 40 | }, 41 | year: 'жыл', 42 | years: function (value) { 43 | return numpf(value, '%d жыл', '%d жыл', '%d жыл') 44 | }, 45 | } 46 | 47 | export default strings 48 | -------------------------------------------------------------------------------- /src/language-strings/fa-short.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const hasIntl = typeof Intl !== 'undefined' && Intl.NumberFormat != null 5 | 6 | // persion shortened 7 | const strings: L10nsStrings = { 8 | prefixAgo: null, 9 | prefixFromNow: null, 10 | suffixAgo: '', 11 | suffixFromNow: '', 12 | seconds: '۱دقیقه', 13 | minute: '۱دقیقه', 14 | minutes: (value) => { 15 | if (hasIntl) { 16 | return new Intl.NumberFormat('fa').format(value) + 'دقیقه' 17 | } 18 | return '%dدقیقه' 19 | }, 20 | hour: '۱ساعت', 21 | hours: (value) => { 22 | if (hasIntl) { 23 | return new Intl.NumberFormat('fa').format(value) + 'ساعت' 24 | } 25 | return '%dساعت' 26 | }, 27 | day: '۱روز', 28 | days: (value) => { 29 | if (hasIntl) { 30 | return new Intl.NumberFormat('fa').format(value) + 'روز' 31 | } 32 | return '%dروز' 33 | }, 34 | month: '۱ماه', 35 | months: (value) => { 36 | if (hasIntl) { 37 | return new Intl.NumberFormat('fa').format(value) + 'ماه' 38 | } 39 | return '%dماه' 40 | }, 41 | year: '۱سال', 42 | years: (value) => { 43 | if (hasIntl) { 44 | return new Intl.NumberFormat('fa').format(value) + 'سال' 45 | } 46 | return '%dسال' 47 | }, 48 | wordSeparator: ' ', 49 | } 50 | 51 | export default strings 52 | -------------------------------------------------------------------------------- /src/language-strings/be.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Belarusian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | // f - 1, 21, 31, ... 7 | // s - 2-4, 22-24, 32-34 ... 8 | // t - 5-20, 25-30, ... 9 | const n10 = n % 10 10 | if (n10 === 1 && (n === 1 || n > 20)) { 11 | return f 12 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 13 | return s 14 | } else { 15 | return t 16 | } 17 | } 18 | 19 | const strings: L10nsStrings = { 20 | prefixAgo: null, 21 | prefixFromNow: 'праз', 22 | suffixAgo: 'таму', 23 | suffixFromNow: null, 24 | seconds: 'менш хвіліны', 25 | minute: 'хвіліну', 26 | minutes: function (value) { 27 | return numpf(value, '%d хвіліна', '%d хвіліны', '%d хвілін') 28 | }, 29 | hour: 'гадзіну', 30 | hours: function (value) { 31 | return numpf(value, '%d гадзіна', '%d гадзіны', '%d гадзін') 32 | }, 33 | day: 'дзень', 34 | days: function (value) { 35 | return numpf(value, '%d дзень', '%d дні', '%d дзён') 36 | }, 37 | month: 'месяц', 38 | months: function (value) { 39 | return numpf(value, '%d месяц', '%d месяцы', '%d месяцаў') 40 | }, 41 | year: 'год', 42 | years: function (value) { 43 | return numpf(value, '%d год', '%d гады', '%d гадоў') 44 | }, 45 | } 46 | 47 | export default strings 48 | -------------------------------------------------------------------------------- /src/language-strings/uk.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Ukrainian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | // f - 1, 21, 31, ... 7 | // s - 2-4, 22-24, 32-34 ... 8 | // t - 5-20, 25-30, ... 9 | const n10 = n % 10 10 | if (n10 === 1 && (n === 1 || n > 20)) { 11 | return f 12 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 13 | return s 14 | } else { 15 | return t 16 | } 17 | } 18 | 19 | const strings: L10nsStrings = { 20 | prefixAgo: null, 21 | prefixFromNow: 'через', 22 | suffixAgo: 'тому', 23 | suffixFromNow: null, 24 | seconds: 'менше хвилини', 25 | minute: 'хвилина', 26 | minutes: function (value) { 27 | return numpf(value, '%d хвилина', '%d хвилини', '%d хвилин') 28 | }, 29 | hour: 'година', 30 | hours: function (value) { 31 | return numpf(value, '%d година', '%d години', '%d годин') 32 | }, 33 | day: 'день', 34 | days: function (value) { 35 | return numpf(value, '%d день', '%d дні', '%d днів') 36 | }, 37 | month: 'місяць', 38 | months: function (value) { 39 | return numpf(value, '%d місяць', '%d місяці', '%d місяців') 40 | }, 41 | year: 'рік', 42 | years: function (value) { 43 | return numpf(value, '%d рік', '%d роки', '%d років') 44 | }, 45 | } 46 | 47 | export default strings 48 | -------------------------------------------------------------------------------- /src/language-strings/rs.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Serbian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | const n10 = n % 10 7 | if (n10 === 1 && (n === 1 || n > 20)) { 8 | return f 9 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 10 | return s 11 | } else { 12 | return t 13 | } 14 | } 15 | 16 | const strings: L10nsStrings = { 17 | prefixAgo: 'pre', 18 | prefixFromNow: 'za', 19 | suffixAgo: null, 20 | suffixFromNow: null, 21 | second: 'sekund', 22 | seconds: function (value) { 23 | return numpf(value, '%d sekund', '%d sekunde', '%d sekundi') 24 | }, 25 | minute: 'oko minut', 26 | minutes: function (value) { 27 | return numpf(value, '%d minut', '%d minuta', '%d minuta') 28 | }, 29 | hour: 'oko jedan sat', 30 | hours: function (value) { 31 | return numpf(value, '%d sat', '%d sata', '%d sati') 32 | }, 33 | day: 'jedan dan', 34 | days: function (value) { 35 | return numpf(value, '%d dan', '%d dana', '%d dana') 36 | }, 37 | month: 'mesec dana', 38 | months: function (value) { 39 | return numpf(value, '%d mesec', '%d meseca', '%d meseci') 40 | }, 41 | year: 'godinu dana', 42 | years: function (value) { 43 | return numpf(value, '%d godinu', '%d godine', '%d godina') 44 | }, 45 | wordSeparator: ' ', 46 | } 47 | 48 | export default strings 49 | -------------------------------------------------------------------------------- /src/language-strings/sr.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Serbian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | const n10 = n % 10 7 | if (n10 === 1 && (n === 1 || n > 20)) { 8 | return f 9 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 10 | return s 11 | } else { 12 | return t 13 | } 14 | } 15 | 16 | const strings: L10nsStrings = { 17 | prefixAgo: 'пре', 18 | prefixFromNow: 'за', 19 | suffixAgo: null, 20 | suffixFromNow: null, 21 | second: 'секунд', 22 | seconds: function (value) { 23 | return numpf(value, '%d секунд', '%d секунде', '%d секунди') 24 | }, 25 | minute: 'један минут', 26 | minutes: function (value) { 27 | return numpf(value, '%d минут', '%d минута', '%d минута') 28 | }, 29 | hour: 'један сат', 30 | hours: function (value) { 31 | return numpf(value, '%d сат', '%d сата', '%d сати') 32 | }, 33 | day: 'један дан', 34 | days: function (value) { 35 | return numpf(value, '%d дан', '%d дана', '%d дана') 36 | }, 37 | month: 'месец дана', 38 | months: function (value) { 39 | return numpf(value, '%d месец', '%d месеца', '%d месеци') 40 | }, 41 | year: 'годину дана', 42 | years: function (value) { 43 | return numpf(value, '%d годину', '%d године', '%d година') 44 | }, 45 | wordSeparator: ' ', 46 | } 47 | 48 | export default strings 49 | -------------------------------------------------------------------------------- /src/language-strings/bs.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Bosnian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | const n10 = n % 10 7 | if (n10 === 1 && (n === 1 || n > 20)) { 8 | return f 9 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 10 | return s 11 | } else { 12 | return t 13 | } 14 | } 15 | 16 | const strings: L10nsStrings = { 17 | prefixAgo: 'prije', 18 | prefixFromNow: 'za', 19 | suffixAgo: null, 20 | suffixFromNow: null, 21 | second: 'sekund', 22 | seconds: function (value) { 23 | return numpf(value, '%d sekund', '%d sekunde', '%d sekundi') 24 | }, 25 | minute: 'oko minut', 26 | minutes: function (value) { 27 | return numpf(value, '%d minut', '%d minute', '%d minuta') 28 | }, 29 | hour: 'oko sat', 30 | hours: function (value) { 31 | return numpf(value, '%d sat', '%d sata', '%d sati') 32 | }, 33 | day: 'oko jednog dana', 34 | days: function (value) { 35 | return numpf(value, '%d dan', '%d dana', '%d dana') 36 | }, 37 | month: 'mjesec dana', 38 | months: function (value) { 39 | return numpf(value, '%d mjesec', '%d mjeseca', '%d mjeseci') 40 | }, 41 | year: 'prije godinu dana ', 42 | years: function (value) { 43 | return numpf(value, '%d godinu', '%d godine', '%d godina') 44 | }, 45 | wordSeparator: ' ', 46 | } 47 | 48 | export default strings 49 | -------------------------------------------------------------------------------- /src/language-strings/hr.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Croatian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | const n10 = n % 10 7 | if (n10 === 1 && (n === 1 || n > 20)) { 8 | return f 9 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 10 | return s 11 | } else { 12 | return t 13 | } 14 | } 15 | 16 | const strings: L10nsStrings = { 17 | prefixAgo: 'prije', 18 | prefixFromNow: 'za', 19 | suffixAgo: null, 20 | suffixFromNow: null, 21 | second: 'sekundu', 22 | seconds: function (value) { 23 | return numpf(value, '%d sekundu', '%d sekunde', '%d sekundi') 24 | }, 25 | minute: 'oko minutu', 26 | minutes: function (value) { 27 | return numpf(value, '%d minutu', '%d minute', '%d minuta') 28 | }, 29 | hour: 'oko jedan sat', 30 | hours: function (value) { 31 | return numpf(value, '%d sat', '%d sata', '%d sati') 32 | }, 33 | day: 'jedan dan', 34 | days: function (value) { 35 | return numpf(value, '%d dan', '%d dana', '%d dana') 36 | }, 37 | month: 'mjesec dana', 38 | months: function (value) { 39 | return numpf(value, '%d mjesec', '%d mjeseca', '%d mjeseci') 40 | }, 41 | year: 'prije godinu dana', 42 | years: function (value) { 43 | return numpf(value, '%d godinu', '%d godine', '%d godina') 44 | }, 45 | wordSeparator: ' ', 46 | } 47 | 48 | export default strings 49 | -------------------------------------------------------------------------------- /src/language-strings/sl.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Slovenian with support for dual 5 | function numpf(n: number, a: Array): string { 6 | return a[ 7 | n % 100 === 1 8 | ? 1 9 | : n % 100 === 2 10 | ? 2 11 | : n % 100 === 3 || n % 100 === 4 12 | ? 3 13 | : 0 14 | ] 15 | } 16 | 17 | const strings: L10nsStrings = { 18 | prefixAgo: null, 19 | prefixFromNow: 'čez', 20 | suffixAgo: 'nazaj', 21 | suffixFromNow: null, 22 | second: 'sekundo', 23 | seconds: function (value) { 24 | return numpf(value, ['%d sekund', '%d sekundo', '%d sekundi', '%d sekunde']) 25 | }, 26 | minute: 'minuto', 27 | minutes: function (value) { 28 | return numpf(value, ['%d minut', '%d minuto', '%d minuti', '%d minute']) 29 | }, 30 | hour: 'eno uro', 31 | hours: function (value) { 32 | return numpf(value, ['%d ur', '%d uro', '%d uri', '%d ure']) 33 | }, 34 | day: 'en dan', 35 | days: function (value) { 36 | return numpf(value, ['%d dni', '%d dan', '%d dneva', '%d dni']) 37 | }, 38 | month: 'en mesec', 39 | months: function (value) { 40 | return numpf(value, ['%d mesecev', '%d mesec', '%d meseca', '%d mesece']) 41 | }, 42 | year: 'eno leto', 43 | years: function (value) { 44 | return numpf(value, ['%d let', '%d leto', '%d leti', '%d leta']) 45 | }, 46 | wordSeparator: ' ', 47 | } 48 | 49 | export default strings 50 | -------------------------------------------------------------------------------- /src/language-strings/fa.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | const hasIntl = typeof Intl !== 'undefined' && Intl.NumberFormat != null 5 | 6 | // Persian 7 | // Use DIR attribute for RTL text in Persian Language for ABBR tag . 8 | // By MB.seifollahi@gmail.com 9 | const strings: L10nsStrings = { 10 | prefixAgo: null, 11 | prefixFromNow: null, 12 | suffixAgo: 'پیش', 13 | suffixFromNow: 'از حال', 14 | seconds: 'کمتر از یک دقیقه', 15 | minute: 'حدود یک دقیقه', 16 | minutes: (value) => { 17 | if (hasIntl) { 18 | return new Intl.NumberFormat('fa').format(value) + ' دقیقه' 19 | } 20 | return '%d دقیقه' 21 | }, 22 | hour: 'حدود یک ساعت', 23 | hours: (value) => { 24 | if (hasIntl) { 25 | return 'حدود %d ساعت'.replace( 26 | '%d', 27 | new Intl.NumberFormat('fa').format(value), 28 | ) 29 | } 30 | return 'حدود %d ساعت' 31 | }, 32 | day: 'یک روز', 33 | days: (value) => { 34 | if (hasIntl) { 35 | return '%d روز'.replace('%d', new Intl.NumberFormat('fa').format(value)) 36 | } 37 | return '%d روز' 38 | }, 39 | month: 'حدود یک ماه', 40 | months: (value) => { 41 | if (hasIntl) { 42 | return '%d ماه'.replace('%d', new Intl.NumberFormat('fa').format(value)) 43 | } 44 | return '%d ماه' 45 | }, 46 | year: 'حدود یک سال', 47 | years: (value) => { 48 | if (hasIntl) { 49 | return '%d سال'.replace('%d', new Intl.NumberFormat('fa').format(value)) 50 | } 51 | return '%d سال' 52 | }, 53 | wordSeparator: ' ', 54 | } 55 | 56 | export default strings 57 | -------------------------------------------------------------------------------- /src/language-strings/ru.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Russian 5 | function numpf(n: number, f: string, s: string, t: string): string { 6 | // f - 1, 21, 31, ... 7 | // s - 2-4, 22-24, 32-34 ... 8 | // t - 5-20, 25-30, ... 9 | const n10 = n % 10 10 | if (n10 === 1 && (n === 1 || n > 20)) { 11 | return f 12 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 13 | return s 14 | } else { 15 | return t 16 | } 17 | } 18 | 19 | const strings: L10nsStrings = { 20 | prefixAgo: null, 21 | prefixFromNow: 'через', 22 | suffixAgo: function suffixAgo(value) { 23 | if (value === 0) { 24 | return '' 25 | } 26 | return 'назад' 27 | }, 28 | suffixFromNow: null, 29 | seconds: function seconds(value) { 30 | if (value === 0) { 31 | return 'только что' 32 | } 33 | return numpf(value, '%d секунду', '%d секунды', '%d секунд') 34 | }, 35 | minute: 'минуту', 36 | minutes: function (value) { 37 | return numpf(value, '%d минуту', '%d минуты', '%d минут') 38 | }, 39 | hour: 'час', 40 | hours: function (value) { 41 | return numpf(value, '%d час', '%d часа', '%d часов') 42 | }, 43 | day: 'день', 44 | days: function (value) { 45 | return numpf(value, '%d день', '%d дня', '%d дней') 46 | }, 47 | month: 'месяц', 48 | months: function (value) { 49 | return numpf(value, '%d месяц', '%d месяца', '%d месяцев') 50 | }, 51 | year: 'год', 52 | years: function (value) { 53 | return numpf(value, '%d год', '%d года', '%d лет') 54 | }, 55 | } 56 | 57 | export default strings 58 | -------------------------------------------------------------------------------- /src/language-strings/cs.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | // Czech 5 | function f(n: number, d: number, a: $ReadOnlyArray) { 6 | return a[d >= 0 ? 0 : a.length === 2 || n < 5 ? 1 : 2] 7 | } 8 | 9 | const strings: L10nsStrings = { 10 | prefixAgo: 'před', 11 | prefixFromNow: 'za', 12 | suffixAgo: null, 13 | suffixFromNow: null, 14 | seconds: function (n: number, d: number) { 15 | return f(n, d, ['méně než minutou', 'méně než minutu']) 16 | }, 17 | minute: function (n: number, d: number) { 18 | return f(n, d, ['minutou', 'minutu']) 19 | }, 20 | minutes: function (n: number, d: number) { 21 | return f(n, d, ['%d minutami', '%d minuty', '%d minut']) 22 | }, 23 | hour: function (n: number, d: number) { 24 | return f(n, d, ['hodinou', 'hodinu']) 25 | }, 26 | hours: function (n: number, d: number) { 27 | return f(n, d, ['%d hodinami', '%d hodiny', '%d hodin']) 28 | }, 29 | day: function (n: number, d: number) { 30 | return f(n, d, ['%d dnem', '%d den']) 31 | }, 32 | days: function (n: number, d: number) { 33 | return f(n, d, ['%d dny', '%d dny', '%d dní']) 34 | }, 35 | month: function (n: number, d: number) { 36 | return f(n, d, ['%d měsícem', '%d měsíc']) 37 | }, 38 | months: function (n: number, d: number) { 39 | return f(n, d, ['%d měsíci', '%d měsíce', '%d měsíců']) 40 | }, 41 | year: function (n: number, d: number) { 42 | return f(n, d, ['%d rokem', '%d rok']) 43 | }, 44 | years: function (n: number, d: number) { 45 | return f(n, d, ['%d lety', '%d roky', '%d let']) 46 | }, 47 | } 48 | 49 | export default strings 50 | -------------------------------------------------------------------------------- /examples/simple/index.js: -------------------------------------------------------------------------------- 1 | /* @flow strict */ 2 | import * as React from 'react' 3 | import { useMemo } from 'react' 4 | import ReactDom from 'react-dom/client' 5 | import TimeAgo from '../../src/index' 6 | import { makeIntlFormatter } from '../../src/defaultFormatter' 7 | 8 | const appElement = document.getElementById('app') 9 | 10 | function Bare({ date }: { date: number }) { 11 | return ( 12 | <> 13 | You opened this page 14 | 15 | ) 16 | } 17 | 18 | const intlFormatter = makeIntlFormatter({ 19 | locale: 'en', 20 | style: 'long', 21 | numeric: 'auto', 22 | }) 23 | 24 | function Intl({ date }: { date: number }) { 25 | const [locale, setLocale] = React.useState('en') 26 | const [style, setStyle] = React.useState('long') 27 | const [numeric, setNumeric] = React.useState('auto') 28 | 29 | const intlFormatter = useMemo( 30 | () => 31 | makeIntlFormatter({ 32 | locale, 33 | style, 34 | numeric, 35 | }), 36 | [locale, style, numeric], 37 | ) 38 | return ( 39 |
40 | 50 | 55 | 59 | 60 |
61 | ) 62 | } 63 | 64 | function App() { 65 | const [date, setDate] = React.useState(Date.now()) 66 | 67 | return ( 68 | <> 69 |

Bare

70 | 71 |

Intl

72 | You opened this page 73 |
74 | 75 | 76 | ) 77 | } 78 | 79 | appElement && ReactDom.createRoot(appElement).render() 80 | -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | pr-test: 12 | # Only run on pull requests 13 | if: github.event_name == 'pull_request' 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 22 23 | 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v2 26 | with: 27 | version: latest 28 | 29 | - name: Install dependencies 30 | run: pnpm install 31 | 32 | - name: Run tests 33 | run: pnpm test 34 | 35 | build: 36 | # Only run on pushes to master or tags 37 | if: github.event_name == 'push' 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v3 42 | 43 | - name: Setup Node.js 44 | uses: actions/setup-node@v3 45 | with: 46 | node-version: 22 47 | 48 | - name: Install pnpm 49 | uses: pnpm/action-setup@v2 50 | with: 51 | version: latest 52 | 53 | - name: Install dependencies 54 | run: pnpm install 55 | 56 | - name: Run tests 57 | run: pnpm test 58 | 59 | deploy: 60 | needs: build 61 | # Only run on master branch and not on pull requests 62 | if: github.ref == 'refs/heads/master' && github.event_name == 'push' 63 | runs-on: ubuntu-latest 64 | 65 | steps: 66 | - uses: actions/checkout@v3 67 | 68 | - name: Setup Node.js 69 | uses: actions/setup-node@v3 70 | with: 71 | node-version: 22 72 | registry-url: 'https://registry.npmjs.org' 73 | 74 | - name: Install pnpm 75 | uses: pnpm/action-setup@v2 76 | with: 77 | version: latest 78 | 79 | - name: Install dependencies 80 | run: pnpm install 81 | 82 | - name: Run tests 83 | run: pnpm test 84 | 85 | - name: Build 86 | run: pnpm run build 87 | 88 | - name: Check if version exists 89 | id: version-check 90 | run: | 91 | PKG_VERSION=$(node -p "require('./package.json').version") 92 | PKG_NAME=$(node -p "require('./package.json').name") 93 | if npm view "$PKG_NAME@$PKG_VERSION" version &> /dev/null; then 94 | echo "Version $PKG_VERSION already exists. Skipping publish." 95 | echo "exists=true" >> $GITHUB_OUTPUT 96 | else 97 | echo "Version $PKG_VERSION does not exist. Proceeding with publish." 98 | echo "exists=false" >> $GITHUB_OUTPUT 99 | fi 100 | 101 | - name: Publish to npm 102 | if: steps.version-check.outputs.exists == 'false' 103 | run: pnpm publish 104 | env: 105 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /src/language-strings/ar.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import type { L10nsStrings } from '../formatters/buildFormatter' 3 | 4 | function numpf(n: number, a: Array): string { 5 | return a[ 6 | n === 0 7 | ? 0 8 | : n === 1 9 | ? 1 10 | : n === 2 11 | ? 2 12 | : n % 100 >= 3 && n % 100 <= 10 13 | ? 3 14 | : n % 100 >= 11 15 | ? 4 16 | : 5 17 | ] 18 | } 19 | 20 | const strings: L10nsStrings = { 21 | prefixAgo: 'منذ', 22 | prefixFromNow: 'بعد', 23 | suffixAgo: null, 24 | suffixFromNow: null, // null OR "من الآن" 25 | second: function (value) { 26 | return numpf(value, [ 27 | 'أقل من ثانية', 28 | 'ثانية واحدة', 29 | 'ثانيتين', 30 | '%d ثوانٍ', 31 | '%d ثانية', 32 | '%d ثانية', 33 | ]) 34 | }, 35 | seconds: function (value) { 36 | return numpf(value, [ 37 | 'أقل من ثانية', 38 | 'ثانية واحدة', 39 | 'ثانيتين', 40 | '%d ثوانٍ', 41 | '%d ثانية', 42 | '%d ثانية', 43 | ]) 44 | }, 45 | minute: function (value) { 46 | return numpf(value, [ 47 | 'أقل من دقيقة', 48 | 'دقيقة واحدة', 49 | 'دقيقتين', 50 | '%d دقائق', 51 | '%d دقيقة', 52 | 'دقيقة', 53 | ]) 54 | }, 55 | minutes: function (value) { 56 | return numpf(value, [ 57 | 'أقل من دقيقة', 58 | 'دقيقة واحدة', 59 | 'دقيقتين', 60 | '%d دقائق', 61 | '%d دقيقة', 62 | 'دقيقة', 63 | ]) 64 | }, 65 | hour: function (value) { 66 | return numpf(value, [ 67 | 'أقل من ساعة', 68 | 'ساعة واحدة', 69 | 'ساعتين', 70 | '%d ساعات', 71 | '%d ساعة', 72 | '%d ساعة', 73 | ]) 74 | }, 75 | hours: function (value) { 76 | return numpf(value, [ 77 | 'أقل من ساعة', 78 | 'ساعة واحدة', 79 | 'ساعتين', 80 | '%d ساعات', 81 | '%d ساعة', 82 | '%d ساعة', 83 | ]) 84 | }, 85 | day: function (value) { 86 | return numpf(value, [ 87 | 'أقل من يوم', 88 | 'يوم واحد', 89 | 'يومين', 90 | '%d أيام', 91 | '%d يومًا', 92 | '%d يوم', 93 | ]) 94 | }, 95 | days: function (value) { 96 | return numpf(value, [ 97 | 'أقل من يوم', 98 | 'يوم واحد', 99 | 'يومين', 100 | '%d أيام', 101 | '%d يومًا', 102 | '%d يوم', 103 | ]) 104 | }, 105 | month: function (value) { 106 | return numpf(value, [ 107 | 'أقل من شهر', 108 | 'شهر واحد', 109 | 'شهرين', 110 | '%d أشهر', 111 | '%d شهرًا', 112 | '%d شهر', 113 | ]) 114 | }, 115 | months: function (value) { 116 | return numpf(value, [ 117 | 'أقل من شهر', 118 | 'شهر واحد', 119 | 'شهرين', 120 | '%d أشهر', 121 | '%d شهرًا', 122 | '%d شهر', 123 | ]) 124 | }, 125 | year: function (value) { 126 | return numpf(value, [ 127 | 'أقل من عام', 128 | 'عام واحد', 129 | '%d عامين', 130 | '%d أعوام', 131 | '%d عامًا', 132 | ]) 133 | }, 134 | years: function (value) { 135 | return numpf(value, [ 136 | 'أقل من عام', 137 | 'عام واحد', 138 | 'عامين', 139 | '%d أعوام', 140 | '%d عامًا', 141 | '%d عام', 142 | ]) 143 | }, 144 | } 145 | 146 | export default strings 147 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | #### v8.3.0 4 | - Exported Formatter, Unit, and Suffix types which were present in v7 5 | 6 | #### v8.1.0 7 | - Fixed bug where `nextFormatter` no longer worked without passing arguments 8 | - Calling `nextFormatter()` without arguments should work once again. 9 | 10 | #### v8.0.0 11 | - Code overhaul 12 | - Moved from `npm` to `pnpm` 13 | - Support React 19.x 14 | - Minor fixes 15 | 16 | #### v7.1.0 17 | - Use Intl to render persian number for farsi languages 18 | - Looking for help testing the results and then doing the same for Arabic 19 | 20 | #### v7.0.0 21 | - Added support for React 18 22 | - Added hi.js language strings 23 | - Added so.js language strings 24 | - Added oc.js language strings 25 | - Update ru.js language strings 26 | - Fix memory leak 27 | - Update dependencies 28 | 29 | #### v6.1.0 30 | - Added `eslint-plugin-react-hooks` and fixed the dependencies for hooks used in the library. 31 | Should result in a more consistent behaviour when changing props at runtime. 32 | - Updated dependencies 33 | 34 | #### v6.0.0 35 | - Added `module` field to the package.json file so you can now import ES6 modules with only 36 | a minimal amount of pre-processing applied. Relevant when targeting newer browsers only. 37 | - A bunch of internal changes: 38 | - Using `rollup` instead of `browserify` to compile the example 39 | - Updating a bunch of dependencies 40 | - Added support for React 17 41 | 42 | #### v5.2.1 43 | 44 | - Minor documentation fixes 45 | 46 | #### v4.4.0 47 | - Bug Fix: clearTimeout when setting a new timeout. Prevents unnecessary renders. 48 | 49 | #### v4.2.0 50 | - Pass in the `now` function as the last argument to the formatters. 51 | - Fix a bug in `buildFormatter` that would ignore the user-specified `now` function and just used `Date.now` 52 | 53 | #### v4.2.0 54 | - Fixed the type of `Formatter`. 55 | - It's last argument is now correctly typed to be `() => React.Node` 56 | - This last argument is now a documented feature and is going to be set to the value of of the default formatter. 57 | - Please Note, that you should not use this argument and instead import defaultFormatter from the package directly and use it as a fallback. 58 | 59 | #### v4.0.0 - v4.1.9: 60 | - Requires React ^16. 61 | - Flow types updated to the latest version (0.69) 62 | - Various bug-fixes. 63 | 64 | #### v3.x.x: 65 | 66 | - `minPeriod` and `maxPeriod` now accept seconds not milliseconds. This matches the documentation. 67 | - react-timeago now uses ES6 modules. So if you don't use ES6, your code will go from : 68 | 69 | ```js 70 | var TimeAgo = require('react-timeago') 71 | ``` 72 | to: 73 | ```js 74 | var TimeAgo = require('react-timeago').default 75 | ``` 76 | ES6 imports will continue to work the same way. 77 | ```js 78 | import TimeAgo from 'react-timeago' 79 | ``` 80 | 81 | #### v2.2.1 82 | * Fixed the many typos introduced by me in 2.2.0. Thanks to insin for the quick PR. 83 | 84 | #### v2.2.0 85 | * FEATURE: New Props: `minPeriod` and `maxPeriod` to customize how often the Component updates. 86 | 87 | #### v2.1.1 88 | 89 | * BUG-FIX: Fixed an issue, where changing the date wouldn't correctly update the update timer. 90 | 91 | #### v2.1.0 92 | 93 | * FEATURE: Added PropType validation. It will now print a warning if you fail to pass in a date, instead of failing silently. 94 | * BUG-FIX: Pending Timeouts are cleared when the Component is unmounted 95 | * BUG-FIX: When new Props are passed in, the component will update itself correctly. Now you can flip the live switch on and off. 96 | * FEATURE: The formatter function gets the original date as the fourth argument, for more custom date formats. 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-timeago", 3 | "version": "8.3.0", 4 | "description": "A simple Time-Ago component for ReactJs", 5 | "main": "lib/index.js", 6 | "types": "es6/index.d.ts", 7 | "exports": { 8 | ".": { 9 | "import": "./es6/index.js", 10 | "require": "./lib/index.js" 11 | }, 12 | "./dateParser": { 13 | "import": "./es6/dateParser.js", 14 | "require": "./lib/dateParser.js" 15 | }, 16 | "./defaultFormatter": { 17 | "import": "./es6/defaultFormatter.js", 18 | "require": "./lib/defaultFormatter.js" 19 | }, 20 | "./language-strings/*": { 21 | "import": "./es6/language-strings/*.js", 22 | "require": "./lib/language-strings/*.js" 23 | }, 24 | "./formatters/buildFormatter": { 25 | "import": "./es6/formatters/buildFormatter.js", 26 | "require": "./lib/formatters/buildFormatter.js" 27 | }, 28 | "./lib/language-strings/*": { 29 | "import": "./es6/language-strings/*.js", 30 | "require": "./lib/language-strings/*.js" 31 | }, 32 | "./lib/formatters/buildFormatter": { 33 | "import": "./es6/formatters/buildFormatter.js", 34 | "require": "./lib/formatters/buildFormatter.js" 35 | } 36 | }, 37 | "module": "es6/index.js", 38 | "scripts": { 39 | "babel-es6": "ES6=true babel src/ --out-dir es6/", 40 | "postbabel-es6": "node ./scripts/gen-types.js -i src -o es6/", 41 | "babel": "babel src/ --out-dir lib/", 42 | "postbabel": "node ./scripts/gen-types.js -i src -o lib/", 43 | "build": "npm run babel && npm run babel-es6 && npm run example", 44 | "example": "rollup -c ./rollup.config.mjs", 45 | "prepublish": "npm run build", 46 | "test": "jest --coverage" 47 | }, 48 | "jest": { 49 | "testEnvironment": "jsdom" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "https://github.com/naman34/react-timeago.git" 54 | }, 55 | "keywords": [ 56 | "React", 57 | "ReactJS", 58 | "Time", 59 | "Ago", 60 | "TimeAgo", 61 | "ender" 62 | ], 63 | "author": "Naman Goel", 64 | "license": "MIT", 65 | "bugs": { 66 | "url": "https://github.com/nmn/react-timeago/issues" 67 | }, 68 | "homepage": "https://github.com/nmn/react-timeago", 69 | "peerDependencies": { 70 | "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 71 | }, 72 | "devDependencies": { 73 | "@babel/cli": "^7.27.0", 74 | "@babel/preset-env": "^7.18.2", 75 | "@babel/preset-flow": "^7.17.12", 76 | "@babel/preset-react": "^7.17.12", 77 | "@babel/preset-stage-1": "^7.8.3", 78 | "@rollup/plugin-babel": "^6.0.4", 79 | "@rollup/plugin-commonjs": "^28.0.2", 80 | "@rollup/plugin-node-resolve": "^16.0.1", 81 | "@rollup/plugin-replace": "^6.0.2", 82 | "@testing-library/jest-dom": "^6.6.3", 83 | "@testing-library/react": "^16.3.0", 84 | "babel-jest": "^29.7.0", 85 | "babel-plugin-syntax-hermes-parser": "^0.28.0", 86 | "eslint-config-prettier": "^10.1.2", 87 | "eslint-plugin-flowtype": "^8.0.3", 88 | "eslint-plugin-ft-flow": "^3.0.0", 89 | "eslint-plugin-import": "^2.26.0", 90 | "eslint-plugin-prettier": "^4.0.0", 91 | "eslint-plugin-react-hooks": "^5.1.0", 92 | "eslint": "^8.1.0", 93 | "flow-api-translator": "^0.28.0", 94 | "flow-bin": "^0.267.0", 95 | "hermes-eslint": "^0.28.0", 96 | "jest-environment-jsdom": "^29.7.0", 97 | "jest": "^29.7.0", 98 | "prettier-plugin-hermes-parser": "^0.28.0", 99 | "prettier": "^2.8.8", 100 | "react-dom": "^19.1.0", 101 | "react": "^19.1.0", 102 | "rollup": "^4.39.0", 103 | "yargs": "^17.7.2" 104 | }, 105 | "prettier": { 106 | "plugins": [ 107 | "prettier-plugin-hermes-parser" 108 | ], 109 | "printWidth": 80, 110 | "semi": false, 111 | "trailingComma": "all", 112 | "singleQuote": true 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | import { render, fireEvent, screen } from '@testing-library/react' 3 | 4 | import React from 'react' 5 | 6 | import TimeAgo from '../src' 7 | import buildFormatter from '../src/formatters/buildFormatter' 8 | import TWStrings from '../src/language-strings/zh-TW' 9 | 10 | describe('TimeAgo', () => { 11 | test('just now', () => { 12 | render() 13 | expect(screen.getByText('0 seconds ago')).toBeInTheDocument() 14 | }) 15 | 16 | test('1 second ago', () => { 17 | render() 18 | expect(screen.getByText('1 second ago')).toBeInTheDocument() 19 | }) 20 | 21 | test('2 seconds ago', () => { 22 | render() 23 | expect(screen.getByText('2 seconds ago')).toBeInTheDocument() 24 | }) 25 | 26 | test('1 minute ago', () => { 27 | render() 28 | expect(screen.getByText('1 minute ago')).toBeInTheDocument() 29 | }) 30 | 31 | test('2 minutes ago', () => { 32 | render() 33 | expect(screen.getByText('2 minutes ago')).toBeInTheDocument() 34 | }) 35 | 36 | test('1 hour ago', () => { 37 | render() 38 | expect(screen.getByText('1 hour ago')).toBeInTheDocument() 39 | }) 40 | 41 | test('2 hours ago', () => { 42 | render() 43 | expect(screen.getByText('2 hours ago')).toBeInTheDocument() 44 | }) 45 | 46 | test('1 day ago', () => { 47 | render() 48 | expect(screen.getByText('1 day ago')).toBeInTheDocument() 49 | }) 50 | 51 | test('1 week ago', () => { 52 | render() 53 | expect(screen.getByText('1 week ago')).toBeInTheDocument() 54 | }) 55 | 56 | test('21 days ago', () => { 57 | const timeAgoFormatConfig = { 58 | prefixAgo: null, 59 | prefixFromNow: null, 60 | suffixAgo: 'ago', 61 | suffixFromNow: 'from now', 62 | seconds: '%d sec', 63 | minutes: '%d min', 64 | hour: '%d hr', 65 | hours: '%d hrs', 66 | day: '%d day', 67 | days: '%d days', 68 | wordSeparator: ' ', 69 | } 70 | 71 | const timeAgoFormatter = buildFormatter(timeAgoFormatConfig) 72 | 73 | render( 74 | new Date('Tue Apr 24 2018 15:12:51 GMT-0400 (EDT)').getTime()} 78 | />, 79 | ) 80 | expect(screen.getByText('21 days ago')).toBeInTheDocument() 81 | }) 82 | 83 | /* zh-TW */ 84 | const formatter = buildFormatter(TWStrings) 85 | 86 | /* 1 week ago in zh-TW */ 87 | it('1 week ago in zh-TW', () => { 88 | render( 89 | , 93 | ) 94 | expect(screen.getByText('7天之前')).toBeInTheDocument() 95 | }) 96 | 97 | test('allow custom wordSeparator', () => { 98 | const strings = Object.assign({}, TWStrings, { wordSeparator: 'x' }) 99 | const formatter = buildFormatter(strings) 100 | render( 101 | , 105 | ) 106 | expect(screen.getByText('7天x之前')).toBeInTheDocument() 107 | }) 108 | 109 | test('on date change, now() should be updated', () => { 110 | const start = 1000000000000 111 | const second = 1_000 112 | const now = jest.fn(() => start + 10 * second) // T+10 113 | 114 | const Component = ({ date }) => <> 115 | 116 | 117 | 118 | const { rerender } = render() 119 | expect(screen.getByText('10 seconds ago')).toBeInTheDocument() 120 | 121 | // advance time by another 10s - T+20 122 | now.mockImplementation(() => start + second * 20) 123 | 124 | // add 1s to previous value 125 | rerender() 126 | 127 | // 20 - 1 = 19, because new now() value should be applied 128 | expect(screen.getByText('19 seconds ago')).toBeInTheDocument() 129 | }) 130 | }) -------------------------------------------------------------------------------- /src/formatters/buildFormatter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { Node as ReactNode } from 'react' 4 | import type { Formatter, Unit, Suffix } from '../types' 5 | 6 | type StringOrFn = string | ((value: number, millisDelta: number) => string) 7 | type NumberArray = [ 8 | string, 9 | string, 10 | string, 11 | string, 12 | string, 13 | string, 14 | string, 15 | string, 16 | string, 17 | string, 18 | ] 19 | 20 | export type L10nsStrings = { 21 | prefixAgo?: ?StringOrFn, 22 | prefixFromNow?: ?StringOrFn, 23 | suffixAgo?: ?StringOrFn, 24 | suffixFromNow?: ?StringOrFn, 25 | second?: ?StringOrFn, 26 | seconds?: ?StringOrFn, 27 | minute?: ?StringOrFn, 28 | minutes?: ?StringOrFn, 29 | hour?: ?StringOrFn, 30 | hours?: ?StringOrFn, 31 | day?: ?StringOrFn, 32 | days?: ?StringOrFn, 33 | week?: ?StringOrFn, 34 | weeks?: ?StringOrFn, 35 | month?: ?StringOrFn, 36 | months?: ?StringOrFn, 37 | year?: ?StringOrFn, 38 | years?: ?StringOrFn, 39 | wordSeparator?: ?string, 40 | numbers?: ?NumberArray, 41 | } 42 | 43 | // If the numbers array is present, format numbers with it, 44 | // otherwise just cast the number to a string and return it 45 | const normalizeNumber = (numbers: ?NumberArray, value: number) => 46 | numbers && numbers.length === 10 47 | ? String(value) 48 | .split('') 49 | .map((digit: string) => 50 | digit.match(/^[0-9]$/) 51 | ? ((numbers: any): NumberArray)[parseInt(digit)] 52 | : digit, 53 | ) 54 | .join('') 55 | : String(value) 56 | 57 | // Take a string or a function that takes number of days and returns a string 58 | // and provide a uniform API to create string parts 59 | const normalizeFn = 60 | (value: number, distanceMillis: number, numbers?: NumberArray) => 61 | (stringOrFn: StringOrFn) => 62 | typeof stringOrFn === 'function' 63 | ? stringOrFn(value, distanceMillis).replace( 64 | /%d/g, 65 | normalizeNumber(numbers, value), 66 | ) 67 | : stringOrFn.replace(/%d/g, normalizeNumber(numbers, value)) 68 | 69 | const pluralize = (unit: Unit) => { 70 | switch (unit) { 71 | case 'second': 72 | return 'seconds' 73 | case 'minute': 74 | return 'minutes' 75 | case 'hour': 76 | return 'hours' 77 | case 'day': 78 | return 'days' 79 | case 'week': 80 | return 'weeks' 81 | case 'month': 82 | return 'months' 83 | case 'year': 84 | return 'years' 85 | default: 86 | return unit 87 | } 88 | } 89 | 90 | export default function buildFormatter(strings: L10nsStrings): Formatter { 91 | return function formatter( 92 | _value: number, 93 | _unit: Unit, 94 | suffix: Suffix, 95 | epochMilliseconds: number, 96 | _nextFormmater: Formatter, 97 | now: () => number, 98 | ) { 99 | const current = now() 100 | let value = _value 101 | let unit = _unit 102 | // convert weeks to days if strings don't handle weeks 103 | if (unit === 'week' && !strings.week && !strings.weeks) { 104 | const days = Math.round( 105 | Math.abs(epochMilliseconds - current) / (1000 * 60 * 60 * 24), 106 | ) 107 | value = days 108 | unit = 'day' 109 | } 110 | 111 | // create a normalize function for given value 112 | const normalize = normalizeFn( 113 | value, 114 | current - epochMilliseconds, 115 | strings.numbers != null ? strings.numbers : undefined, 116 | ) 117 | 118 | // The eventual return value stored in an array so that the wordSeparator can be used 119 | const dateString: Array = [] 120 | 121 | // handle prefixes 122 | if (suffix === 'ago' && strings.prefixAgo) { 123 | dateString.push(normalize(strings.prefixAgo)) 124 | } 125 | if (suffix === 'from now' && strings.prefixFromNow) { 126 | dateString.push(normalize(strings.prefixFromNow)) 127 | } 128 | 129 | // Handle Main number and unit 130 | const isPlural = value > 1 131 | if (isPlural) { 132 | const stringFn: StringOrFn = 133 | strings[pluralize(unit)] || strings[unit] || '%d ' + unit 134 | dateString.push(normalize(stringFn)) 135 | } else { 136 | const stringFn: StringOrFn = 137 | strings[unit] || strings[pluralize(unit)] || '%d ' + unit 138 | dateString.push(normalize(stringFn)) 139 | } 140 | 141 | // Handle Suffixes 142 | if (suffix === 'ago' && strings.suffixAgo) { 143 | dateString.push(normalize(strings.suffixAgo)) 144 | } 145 | if (suffix === 'from now' && strings.suffixFromNow) { 146 | dateString.push(normalize(strings.suffixFromNow)) 147 | } 148 | 149 | // join the array into a string and return it 150 | const wordSeparator = 151 | typeof strings.wordSeparator === 'string' ? strings.wordSeparator : ' ' 152 | return dateString.join(wordSeparator) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /scripts/gen-types.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fsPromises = require('fs/promises') 4 | const monorepoPackage = require('../package.json') 5 | const path = require('path') 6 | const translate = require('flow-api-translator') 7 | const yargs = require('yargs/yargs') 8 | 9 | const prettierConfig = { 10 | ...monorepoPackage.prettier, 11 | // semi: true, 12 | } 13 | 14 | async function generateTypes(inputDir, outputDir, rootDir) { 15 | const rootPath = rootDir ?? path.resolve(inputDir, '../') 16 | 17 | await fsPromises.mkdir(outputDir, { recursive: true }) 18 | let dirents = await fsPromises.readdir(inputDir, { withFileTypes: true }) 19 | 20 | const jsFlowFiles = dirents 21 | .filter((dirent) => dirent.name.endsWith('.js.flow')) 22 | .map((dirent) => dirent.name.replace(/\.js\.flow$/, '.js')) 23 | 24 | const dTsFiles = dirents 25 | .filter((dirent) => dirent.name.endsWith('.d.ts')) 26 | .map((dirent) => dirent.name) 27 | 28 | dirents = dirents.filter((dirents) => !jsFlowFiles.includes(dirents.name)) 29 | 30 | for (const dirent of dirents) { 31 | try { 32 | const inputFullPath = path.join(inputDir, dirent.name) 33 | const outputFullPath = path 34 | .join(outputDir, dirent.name) 35 | .replace(/\.js\.flow$/, '.js') 36 | if (dirent.isDirectory()) { 37 | if (dirent.name !== '__tests__') { 38 | await generateTypes(inputFullPath, outputFullPath, rootPath) 39 | } 40 | } else { 41 | // // dirent is a file 42 | if (dirent.name.endsWith('.d.ts')) { 43 | const fileContents = await fsPromises.readFile(inputFullPath, 'utf8') 44 | await fsPromises.writeFile( 45 | path.join(outputDir, dirent.name), 46 | fileContents, 47 | ) 48 | } 49 | 50 | if (dirent.name.endsWith('.js') || dirent.name.endsWith('.js.flow')) { 51 | try { 52 | let fileContents = await fsPromises.readFile(inputFullPath, 'utf8') 53 | fileContents = preprocessFileContents(fileContents) 54 | const outputFlowContents = await translate.translateFlowToFlowDef( 55 | fileContents, 56 | prettierConfig, 57 | ) 58 | 59 | await fsPromises.writeFile( 60 | `${outputFullPath}.flow`, 61 | outputFlowContents, 62 | ) 63 | const tsOutputName = dirent.name 64 | .replace(/\.js$/, '.d.ts') 65 | .replace(/\.js\.flow$/, '.d.ts') 66 | if (dTsFiles.includes(tsOutputName)) { 67 | continue 68 | } 69 | const outputTSContents = await translate.translateFlowToTSDef( 70 | fileContents, 71 | prettierConfig, 72 | ) 73 | await fsPromises.writeFile( 74 | outputFullPath.replace(/\.js$/, '.d.ts'), 75 | // Typescript Prefers `NodePath` unlike `NodePath<>` in Flow 76 | // `flow-api-translator` doesn't handle this case yet. 77 | postProcessTSOutput(outputTSContents), 78 | ) 79 | } catch (err) { 80 | console.log(`Failed to process file: ${inputFullPath}`) 81 | throw err 82 | } 83 | } 84 | } 85 | } catch (err) { 86 | console.error(`Failed to process file: ${dirent.name}`) 87 | console.error(err) 88 | } 89 | } 90 | } 91 | 92 | // Changes to files before they are processed by `flow-api-translator` 93 | // to patch the bugs in the translator 94 | function preprocessFileContents(inputCode) { 95 | // `flow-api-translator` doesn't handle Flow comments correctly 96 | while (inputCode.includes('/*::')) { 97 | const startIndex = inputCode.indexOf('/*::') 98 | const endIndex = inputCode.indexOf('*/', startIndex) 99 | 100 | const comment = inputCode.substring(startIndex, endIndex + 2) 101 | const replacement = comment.substring(4, comment.length - 2) 102 | 103 | inputCode = inputCode.replace(comment, replacement) 104 | } 105 | return inputCode 106 | } 107 | 108 | function postProcessTSOutput(outputCode) { 109 | const result = outputCode 110 | .replace(/<>/g, '') 111 | .replace(/\$ReadOnlyMap/g, 'ReadonlyMap') 112 | .replace(/\$ReadOnlySet/g, 'ReadonlySet') 113 | 114 | return result 115 | } 116 | 117 | const args = yargs(process.argv) 118 | .option('inputDir', { 119 | alias: 'i', 120 | type: 'string', 121 | }) 122 | .option('outputDir', { 123 | alias: 'o', 124 | type: 'string', 125 | }).argv 126 | 127 | const inputDir = path.join(process.cwd(), args.inputDir) 128 | const outputDir = path.join(process.cwd(), args.outputDir) 129 | generateTypes(inputDir, outputDir) 130 | .then(() => { 131 | console.log('Done generating type definition files') 132 | }) 133 | .catch((err) => { 134 | console.error(err) 135 | process.exit(1) 136 | }) 137 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import * as React from 'react' 4 | import { useEffect, useState } from 'react' 5 | import dateParser from './dateParser' 6 | import defaultFormatter from './defaultFormatter' 7 | export type { Formatter, Suffix, Unit } from './types' 8 | 9 | export type Props = $ReadOnly<{ 10 | /** If the component should update itself over time */ 11 | live?: boolean, 12 | /** minimum amount of time in seconds between re-renders */ 13 | minPeriod?: number, 14 | /** Maximum time between re-renders in seconds. The component should update at least once every `x` seconds */ 15 | maxPeriod?: number, 16 | /** The container to render the string into. You could use a string like `span` or a custom component */ 17 | component?: string | React.ComponentType<{ ... }>, 18 | /** 19 | * A title used for setting the title attribute if a