Skip to content

Commit

Permalink
feat(useTimeAgo): support more language messages, by passing internal…
Browse files Browse the repository at this point in the history
… `messages` to props
  • Loading branch information
vikiboss committed Sep 23, 2024
1 parent 7383c57 commit 9e0a1e9
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 40 deletions.
99 changes: 87 additions & 12 deletions packages/react-use/src/use-time-ago/demo.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,98 @@
import { Card, KeyValue } from '@/components'
import { useTimeAgo } from '@shined/react-use'
import { Card, KeyValue, Zone } from '@/components'
import { CHINESE_MESSAGES, CHINESE_TRADITIONAL_MESSAGES, JAPANESE_MESSAGES, useTimeAgo } from '@shined/react-use'
import { useRef } from 'react'

export function App() {
const threeMinutesAgo = useRef(Date.now() - 3 * 60_000)
const twoYearsAgo = useRef(Date.now() - 2 * 365 * 24 * 60 * 60 * 1000)
const threeMonthsAgo = useRef(Date.now() - 3 * 30 * 24 * 60 * 60 * 1000)
const fourDayAgo = useRef(Date.now() - 4 * 24 * 60 * 60 * 1000)
const fiveHoursAgo = useRef(Date.now() - 5 * 60 * 60 * 1000)
const sixMinutesAgo = useRef(Date.now() - 6 * 60 * 1000)
const tenSecondsAgo = useRef(Date.now() - 10_000)
const inSixMinutes = useRef(Date.now() + 6 * 60 * 1000)
const inFiveHours = useRef(Date.now() + 5 * 60 * 60 * 1000)
const inFourDays = useRef(Date.now() + 4 * 24 * 60 * 60 * 1000)
const inThreeMonths = useRef(Date.now() + 3 * 30 * 24 * 60 * 60 * 1000)
const inTwoYears = useRef(Date.now() + 2 * 365 * 24 * 60 * 60 * 1000)

const results = [
useTimeAgo(threeMinutesAgo.current),
useTimeAgo('2023-01-01T00:00:00Z'),
useTimeAgo('2024-06-01T00:00:00Z'),
useTimeAgo('2024-12-01T00:00:00Z'),
useTimeAgo('2027-01-01T00:00:00Z'),
const engResults = [
useTimeAgo(twoYearsAgo.current),
useTimeAgo(threeMonthsAgo.current),
useTimeAgo(fourDayAgo.current),
useTimeAgo(fiveHoursAgo.current),
useTimeAgo(sixMinutesAgo.current),
useTimeAgo(tenSecondsAgo.current),
useTimeAgo(inSixMinutes.current),
useTimeAgo(inFiveHours.current),
useTimeAgo(inFourDays.current),
useTimeAgo(inThreeMonths.current),
useTimeAgo(inTwoYears.current),
]

const chineseResults = [
useTimeAgo(twoYearsAgo.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(threeMonthsAgo.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(fourDayAgo.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(fiveHoursAgo.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(sixMinutesAgo.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(tenSecondsAgo.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(inSixMinutes.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(inFiveHours.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(inFourDays.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(inThreeMonths.current, { messages: CHINESE_MESSAGES }),
useTimeAgo(inTwoYears.current, { messages: CHINESE_MESSAGES }),
]

const chineseTraditionalResults = [
useTimeAgo(twoYearsAgo.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(threeMonthsAgo.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(fourDayAgo.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(fiveHoursAgo.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(sixMinutesAgo.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(tenSecondsAgo.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(inSixMinutes.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(inFiveHours.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(inFourDays.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(inThreeMonths.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
useTimeAgo(inTwoYears.current, { messages: CHINESE_TRADITIONAL_MESSAGES }),
]

const japaneseResults = [
useTimeAgo(twoYearsAgo.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(threeMonthsAgo.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(fourDayAgo.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(fiveHoursAgo.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(sixMinutesAgo.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(tenSecondsAgo.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(inSixMinutes.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(inFiveHours.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(inFourDays.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(inThreeMonths.current, { messages: JAPANESE_MESSAGES }),
useTimeAgo(inTwoYears.current, { messages: JAPANESE_MESSAGES }),
]

return (
<Card>
{results.map((result, idx) => (
<KeyValue key={result} label={`Result ${idx + 1}`} value={result} />
))}
<Zone border="primary">
{engResults.map((result, idx) => (
<KeyValue key={result} label={`EN Result ${idx + 1}`} value={result} />
))}
</Zone>
<Zone border="amber">
{chineseResults.map((result, idx) => (
<KeyValue key={result} label={`CN Result ${idx + 1}`} value={result} />
))}
</Zone>
<Zone border="blue">
{chineseTraditionalResults.map((result, idx) => (
<KeyValue key={result} label={`CN Traditional Result ${idx + 1}`} value={result} />
))}
</Zone>
<Zone border="red">
{japaneseResults.map((result, idx) => (
<KeyValue key={result} label={`JP Result ${idx + 1}`} value={result} />
))}
</Zone>
</Card>
)
}
4 changes: 3 additions & 1 deletion packages/react-use/src/use-time-ago/format-time-ago.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// mainly from https://github.com/vueuse/vueuse/blob/main/packages/core/useTimeAgo/index.ts

import { isFunction, isNumber } from '../utils/basic'

export type TimeAgoFormatter<T = number> = (value: T, isPast: boolean) => string
Expand Down Expand Up @@ -62,7 +64,7 @@ const DEFAULT_UNITS: TimeAgoUnit<TimeAgoUnitNamesDefault>[] = [
{ max: Number.POSITIVE_INFINITY, value: 31536000000, name: 'year' },
]

const DEFAULT_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = {
export const DEFAULT_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = {
justNow: 'just now',
past: (n) => (n.match(/\d/) ? `${n} ago` : n),
future: (n) => (n.match(/\d/) ? `in ${n}` : n),
Expand Down
71 changes: 52 additions & 19 deletions packages/react-use/src/use-time-ago/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,64 +9,97 @@ import { HooksType } from '@/components'

<HooksType {...frontmatter} />

A React Hook that helps to format a date to a human-readable time ago string. It will automatically update the time ago string every 30 seconds by default.
A React Hook that helps format dates into readable "time ago" strings. By default, it updates this "time ago" string every 30 seconds automatically.

## Demo
## Demo \{#demo}

import { App } from './demo'

<App />

## Usage
## Usage \{#usage}

See API for more details.
```tsx
const timeAgo = useTimeAgo(dateLike, options)

const targetDate = new Date() - 1000 * 60 * 60 * 24 // yesterday

const timeAgo = useTimeAgo(targetDate)
console.log(timeAgo) // "yesterday"

// Custom language, here using Simplified Chinese
import { CHINESE_MESSAGES } from '@shined/react-use'
const timeAgoInChinese = useTimeAgo(targetDate, { messages: CHINESE_MESSAGES })
console.log(timeAgoInChinese) // "昨天"
```

Currently, **Simplified Chinese**, **Traditional Chinese**, **English**, and **Japanese** are built in. If needed, you can customize via `options.messages`. Below is a simple example of formatting in Chinese:

```tsx
/**
* Default messages for Chinese Simplified language
*/
export const CHINESE_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = {
justNow: '刚刚',
past: (n) => (n.match(/\d/) ? `${n}前` : n),
future: (n) => (n.match(/\d/) ? `${n}后` : n),
month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`),
year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`),
day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`),
week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`),
hour: (n) => `${n} 小时`,
minute: (n) => `${n} 分钟`,
second: (n) => `${n} 秒`,
invalid: '',
}
```

## Source
## Source \{#source}

import { Source } from '@/components'

<Source />

## API
## API \{#api}

```tsx
const timeAgo = useTimeAgo(dateLike, options)
const { timeAgo, ...pausable } = useTimeAgo(dateLike, { controls: true, ...otherOptions })
```

### DateLike
### ElementTarget \{#element-target}

```tsx
// like new Date(), 1_612_137_600_000, '2021-01-01', undefined, null
// Similar to new Date(), 1_612_137_600_000, '2021-01-01', undefined, null
export type DateLike = Date | number | string | undefined | null
```
### Options
### Options \{#options}
```tsx
export type FormatTimeAgoOptions<UnitNames extends string = TimeAgoUnitNamesDefault> = {
/**
* Maximum unit (of diff in milliseconds) to display the full date instead of relative
* Maximum unit (millisecond difference), beyond which will display the full date instead of relative time
*
* @defaultValue undefined
*/
max?: UnitNames | number
/**
* Formatter for full date
* Formatter for the full date
*/
fullDateFormatter?: TimeAgeFullDateFormatter
/**
* Messages for formatting the string
* Messages for formatting strings
*/
messages?: TimeAgoMessages<UnitNames>
/**
* Minimum display time unit (default is minute)
* The smallest displayed time unit (default is minute)
*
* @defaultValue false
*/
showSecond?: boolean
/**
* Rounding method to apply.
* Rounding method applied.
*
* @defaultValue 'round'
*/
Expand All @@ -80,25 +113,25 @@ export type FormatTimeAgoOptions<UnitNames extends string = TimeAgoUnitNamesDefa
export interface UseTimeAgoOptions<Controls extends boolean, UnitNames extends string = TimeAgoUnitNamesDefault>
extends FormatTimeAgoOptions<UnitNames> {
/**
* Expose more controls
* Exposes additional controls
*
* @defaultValue false
*/
controls?: Controls
/**
* Intervals to update, set 0 to disable auto update
* Update interval, set to 0 to disable automatic updates
*
* @defaultValue 30_000
*/
updateInterval?: number
}
```

### Returns
### Returns \{#returns}

Retuerns contain [Pausable](/docs/features/pausable) instance that can be paused, resumed.
The return value includes a [Pausable](/docs/features/pausable) instance that can be paused and resumed.

See [Pausable](/docs/features/pausable) for more details.
For more details, please refer to [Pausable](/docs/features/pausable).

```tsx
export type UseTimeAgoReturns<Controls extends boolean = false> = Controls extends true
Expand Down
68 changes: 61 additions & 7 deletions packages/react-use/src/use-time-ago/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { normalizeDate } from '../use-date-format'
import { useNow } from '../use-now'
import { unwrapGettable } from '../utils/unwrap'

import { formatTimeAgo } from './format-time-ago'
export { formatTimeAgo } from './format-time-ago'

import type { FormatTimeAgoOptions, TimeAgoUnitNamesDefault } from './format-time-ago'
export type { FormatTimeAgoOptions } from './format-time-ago'
import { normalizeDate } from '../use-date-format'
import { unwrapGettable } from '../utils/unwrap'
import { DEFAULT_MESSAGES, formatTimeAgo } from './format-time-ago'

import type { DateLike } from '../use-date-format'
import type { Pausable } from '../use-pausable'
import type { Gettable } from '../utils/basic'
export type { FormatTimeAgoOptions } from './format-time-ago'
import type { FormatTimeAgoOptions, TimeAgoMessages, TimeAgoUnitNamesDefault } from './format-time-ago'

export type UseTimeAgoReturns<Controls extends boolean = false> = Controls extends true
? { timeAgo: string } & Pausable
Expand Down Expand Up @@ -54,3 +52,59 @@ export function useTimeAgo<UnitNames extends string = TimeAgoUnitNamesDefault>(

return exposeControls ? { timeAgo, ...controls } : timeAgo
}

/**
* Default messages for Chinese Simplified language
*/
export const CHINESE_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = {
justNow: '刚刚',
past: (n) => (n.match(/\d/) ? `${n}前` : n),
future: (n) => (n.match(/\d/) ? `${n}后` : n),
month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`),
year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`),
day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`),
week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`),
hour: (n) => `${n} 小时`,
minute: (n) => `${n} 分钟`,
second: (n) => `${n} 秒`,
invalid: '',
}

/**
* Default messages for Chinese Traditional language
*/
export const CHINESE_TRADITIONAL_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = {
justNow: '剛剛',
past: (n) => (n.match(/\d/) ? `${n}前` : n),
future: (n) => (n.match(/\d/) ? `${n}後` : n),
month: (n, past) => (n === 1 ? (past ? '上個月' : '下個月') : `${n} 個月`),
year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`),
day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`),
week: (n, past) => (n === 1 ? (past ? '上週' : '下週') : `${n} 週`),
hour: (n) => `${n} 小時`,
minute: (n) => `${n} 分鐘`,
second: (n) => `${n} 秒`,
invalid: '',
}

/**
* Default messages for Japanese language
*/
export const JAPANESE_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = {
justNow: 'たった今',
past: (n) => (n.match(/\d/) ? `${n}前` : n),
future: (n) => (n.match(/\d/) ? `${n}後` : n),
month: (n, past) => (n === 1 ? (past ? '先月' : '来月') : `${n} ヶ月`),
year: (n, past) => (n === 1 ? (past ? '去年' : '来年') : `${n} 年`),
day: (n, past) => (n === 1 ? (past ? '昨日' : '明日') : `${n} 日`),
week: (n, past) => (n === 1 ? (past ? '先週' : '来週') : `${n} 週間`),
hour: (n) => `${n} 時間`,
minute: (n) => `${n} 分`,
second: (n) => `${n} 秒`,
invalid: '',
}

/**
* Default messages for English language
*/
export const ENGLISH_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = DEFAULT_MESSAGES
35 changes: 34 additions & 1 deletion packages/react-use/src/use-time-ago/index.zh-cn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,40 @@ import { App } from './demo'

## 用法 \{#usage}

请查看 API。
```tsx
const timeAgo = useTimeAgo(dateLike, options)

const targetDate = new Date() - 1000 * 60 * 60 * 24 // 昨天

const timeAgo = useTimeAgo(targetDate)
console.log(timeAgo) // "yesterday"

// 自定义语言,这里使用简体中文
import { CHINESE_MESSAGES } from '@shined/react-use'
const timeAgoInChinese = useTimeAgo(targetDate, { messages: CHINESE_MESSAGES })
console.log(timeAgoInChinese) // "昨天"
```

目前内置了**简体中文****繁体中文****英文****日文**四种语言,如果需要,可以通过 `options.messages` 进行自定义,以下是一个简单的例子,使用中文进行格式化:

```tsx
/**
* Default messages for Chinese Simplified language
*/
export const CHINESE_MESSAGES: TimeAgoMessages<TimeAgoUnitNamesDefault> = {
justNow: '刚刚',
past: (n) => (n.match(/\d/) ? `${n}前` : n),
future: (n) => (n.match(/\d/) ? `${n}后` : n),
month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`),
year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`),
day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`),
week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`),
hour: (n) => `${n} 小时`,
minute: (n) => `${n} 分钟`,
second: (n) => `${n} 秒`,
invalid: '',
}
```

## 源码 \{#source}

Expand Down

0 comments on commit 9e0a1e9

Please sign in to comment.