EvanHahn / HumanizeDuration.js

361000 becomes "6 minutes, 1 second"
https://evanhahn.github.io/HumanizeDuration.js/
The Unlicense
1.65k stars 175 forks source link

Allow a fallback for fault tolerance #149

Closed Aidurber closed 5 years ago

Aidurber commented 5 years ago

It's useful to provide a fallback language instead of throwing. Before, the other alternative was wrapping the humanizeDuration in a try catch and trying again with a known locale. This pull request add support for a fallback language in case of failure. If the fallback language cannot be found, then we proceed to throw as normal.

Tests added and docs updated.

humanizeDuration(3000, { language: 'bad language', fallback: 'en' })  // '3 seconds'
humanizeDuration(3000, { language: 'EN', fallback: 'en' })  // '3 seconds'
Aidurber commented 5 years ago

@EvanHahn I've done some changes to support an array of language and an array of fallback. An array of language does exactly the same thing as the fallback.

So the solution would be to either make language an array or make fallback an array, but that's essentially the same thing.

The implementation isn't pretty either, though there is probably a cleaner way to do it. My brain isn't working properly 😬:

  // Compare a collection of languages against the specified type (language, or fallback)
  function arrayHasLanguage (collection, languageCompare) {
    var count = collection.length
    var i = 0
    var value
    // Allow an override, useful for switching between options.languages and languages
    var target = languageCompare || languages
    for (i; i < count; i++) {
      value = collection[i]
      // Check if languages has the array value
      if (target.hasOwnProperty(value)) {
        return target[value]
      }
    }
  }
  // Get the dictionary from the supplied language(s) and fallback(s)
  function getDictionary (options) {
    var dictionary
    var match
    if (Array.isArray(options.language) && (match = arrayHasLanguage(options.language, options.languages))) {
      dictionary = match
    } else if (Array.isArray(options.language) && (match = arrayHasLanguage(options.language))) {
      dictionary = match
    } else if (Array.isArray(options.fallback) && (match = arrayHasLanguage(options.fallback, options.languages))) {
      dictionary = match
    } else if (Array.isArray(options.fallback) && (match = arrayHasLanguage(options.fallback))) {
      dictionary = match
    } else if (options.languages.hasOwnProperty(options.language)) {
      dictionary = options.languages[options.language]
    } else if (languages.hasOwnProperty(options.language)) {
      dictionary = languages[options.language]
    } else if (options.languages.hasOwnProperty(options.fallback)) {
      dictionary = options.languages[options.fallback]
    } else if (languages.hasOwnProperty(options.fallback)) {
      dictionary = languages[options.fallback]
    }

    if (!dictionary) {
      throw new Error('No language ' + dictionary + '.')
    }
    return dictionary
  }

Here are the passing tests:

it('accepts fallback languages', () => {
    const h = humanizer()

    assert.strictEqual(h(10000, { language: 'es', fallback: 'en' }), '10 segundos')
    assert.strictEqual(h(10000, { language: 'BAD', fallback: 'en' }), '10 seconds')
  })
  it('accepts an array of fallback languages', () => {
    const h = humanizer()
    assert.strictEqual(h(10000, { language: 'es', fallback: 'en' }), '10 segundos')
    assert.strictEqual(h(10000, { language: 'BAD', fallback: ['es', 'en'] }), '10 segundos')
    assert.strictEqual(h(10000, { language: 'BAD', fallback: ['BAD', 'BAD', 'en'] }), '10 seconds')
  })
  it('accepts an array of languages', () => {
    const h = humanizer({
      languages: {
        klingon: {
          y: () => 'DIS',
          mo: () => 'maqtagh',
          w: () => 'Hogh',
          d: () => 'jaj',
          h: () => 'rep',
          m: () => 'tup',
          s: () => 'cha\'DIch',
          ms: () => 'ms'
        }
      }
    })

    assert.strictEqual(h(10000, { language: ['es', 'en'] }), '10 segundos')
    assert.strictEqual(h(10000, { language: ['BAD', 'es'] }), '10 segundos')
    assert.strictEqual(h(10000, { language: ['BAD', 'BAD'], fallback: 'en' }), '10 seconds')
    assert.strictEqual(h(10000, { language: ['BAD', 'klingon'], fallback: 'en' }), '10 cha\'DIch')
    assert.strictEqual(h(10000, { language: ['BAD'], fallback: ['klingon', 'en'] }), '10 cha\'DIch')
    assert.strictEqual(h(10000, { language: ['BAD'], fallback: 'klingon' }), '10 cha\'DIch')
  })
EvanHahn commented 5 years ago

I think fallbacks being an array would be perfect. It'd look something like this:

// ✅ These are valid (and should be tested)
humanizeDuration(100, { language: 'es' })
humanizeDuration(100, { language: 'es', fallbacks: ['en', 'fr'] })

// 🛑 These are invalid (and should be tested)
humanizeDuration(100, { language: 'es', fallbacks: 'en' })
humanizeDuration(100, { fallbacks: ['es'] })
humanizeDuration(100, { fallbacks: [] })
humanizeDuration(100, { language: ['es', 'en'] })

The tests are the hard part—would you be able to do that part?

Aidurber commented 5 years ago

@EvanHahn I think I get what you mean, please see the latest commit.

Aidurber commented 5 years ago

Hi Evan,

I've incorporated all of your changes (hopefully, I'm hungover and my brain isn't working properly). 😄

EvanHahn commented 5 years ago

Apologies for the delay, but I am on a trip and don't have my personal computer with me. I'll plan to deploy this when I get back (the week of January 7).

Aidurber commented 5 years ago

@EvanHahn Please don't apologise, enjoy your holiday! There's no rush.

EvanHahn commented 5 years ago

This has been released in humanize-duration@3.17.0. Thanks so much! Please add yourself to the credits in the readme if you have a moment.