Skip to content

Commit

Permalink
Merge 8b1d376 into 925ce30
Browse files Browse the repository at this point in the history
  • Loading branch information
nexdrew committed Feb 11, 2019
2 parents 925ce30 + 8b1d376 commit 5930c55
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 38 deletions.
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ s.get(Strings.EXTENDED_AMOUNT) //=> Revenue
Each method can also accept an additional options object supporting the following properties:

- `plural` (boolean) or `count` (number): to more easily support conditional plurality using `get()`
- `suffix` (string): to customize what gets added to the value when auto-pluralization is used
- `lc` (boolean) or `uc` (boolean): to transform the display value to lowercase or uppercase, respectively
- `abbrev` (boolean): to abbreviate the display value to the first letter of each word in the string
- `strict` (boolean, default `true`): to use an empty string (strict=true) or key (strict=false) when key not found in strings or defaults
- `locale` (string): to make sure case-sensitivity respects rules and characters for the user's language and region

## Primary API
Expand All @@ -94,9 +96,11 @@ For convenience, the first two arguments are interchangeable.

- `plural` (boolean): whether to use the plural form or not (mutually exclusive with `count`)
- `count` (number): number of items that should be translated for plurality (mutually exclusive with `plural`)
- `suffix` (string): a custom suffix to add to the display string value when plurality is needed and no explicit plural value is defined
- `lc` (boolean): transform the display string to lowercase (mutually exclusive with `uc`)
- `uc` (boolean): transform the display string to uppercase (mutually exclusive with `lc`)
- `abbrev` (boolean): transform the display string to its abbreviated form
- `strict` (boolean, default `true`): whether keys should be interpreted strictly (required in strings or defaults) or loosely (not required in strings or defaults) - in strict mode, an empty string will be returned when key is not found; in loose mode, the key will be used as the value when key is not found
- `locale` (string): the user's locale e.g. `'en-US'` or `'en_US'`

### `Strings.getSingular(strings, key, opts)`
Expand Down Expand Up @@ -155,7 +159,9 @@ Shortcut to get the plural value defined for `key`. If a singular value is defin

## Supported Keys

- `Strings.ADJUSTMENT`
- `Strings.ANNUAL_CONTRACT_VALUE`
- `Strings.BATCH`
- `Strings.CATEGORY`
- `Strings.CLOSED`
- `Strings.COMPENSATION`
Expand Down Expand Up @@ -184,6 +190,10 @@ Abbreviate the given string by grabbing the first letter/character of each word,

E.g. turns `'Annual Contract Value'` into `'ACV'`

### `Strings.formatInt(int, locale)`

Attempts to format the given integer into a string per the given locale.

### `Strings.isUpper(str, locale)`

Returns a boolean indicating whether the given string is uppercase (in the given locale) or not.
Expand All @@ -194,16 +204,30 @@ Naively attempts to normalize the given locale string into the language tag form

E.g. turns `'en_US'` into `'en-US'`

### `Strings.pluralize(str, locale)`
### `Strings.pluralize(count, noun, opts)`

Attempts to apply pluralization to the given string in a case-sensitive manner.
Combine the given count and noun into a single formatted string, pluralizing the noun if necessary.

E.g. turns `'plan'` into `'plans'`, turns `'box'` into `'boxes'`, and turns `'fly'` into `'flies'`
Supported options include:

- `suffix` (string): add this to the string instead of making a best-guess effort when pluralization is needed
- `locale` (string): to format the integer and respect case-sensitivity in a language-specific way

### `Strings.toLower(str, locale)`

Attempts to safely transform the given string into lowercase. If a locale is given, it will be normalized. If the locale-specific operation fails, it will fall back to a locale-agnostic operation.

### `Strings.toPlural(str, opts)`

Transforms the given string into its plural form, respecting case.

E.g. turns `'plan'` into `'plans'`, turns `'box'` into `'boxes'`, and turns `'fly'` into `'flies'`

Supported options include:

- `suffix` (string): add this to the string instead of making a best-guess effort
- `locale` (string): to respect case-sensitivity in a language-specific way

### `Strings.toUpper(str, locale)`

Attempts to safely transform the given string into uppercase. If a locale is given, it will be normalized. If the locale-specific operation fails, it will fall back to a locale-agnostic operation.
Expand Down
98 changes: 77 additions & 21 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,66 @@ class Strings {
return String(c) !== Strings.toLower(c, locale)
}

static pluralize (str, locale) {
// valid opts:
// - suffix (string, default based on str) to customize pluralization
// - locale (string, no default) to respect user's language + region
static toPlural (str, opts) {
if (!str) return ''
let add = 's'
opts = opts || {}
str = String(str)
const last = str.slice(-1)
if (~['y', 'Y'].indexOf(last)) {
str = str.slice(0, -1)
add = 'ies'
} else if (~['j', 'o', 's', 'x', 'z', 'J', 'O', 'S', 'X', 'Z'].indexOf(last)) {
add = 'es'
let add
if (opts.suffix) add = opts.suffix
else {
add = 's'
if (~['y', 'Y'].indexOf(last)) {
str = str.slice(0, -1)
add = 'ies'
} else if (~['h', 'j', 'o', 's', 'x', 'z', 'H', 'J', 'O', 'S', 'X', 'Z'].indexOf(last)) {
add = 'es'
}
}
return Strings.isUpper(last, locale) ? str + Strings.toUpper(add, locale) : str + add
return Strings.isUpper(last, opts.locale) ? str + Strings.toUpper(add, opts.locale) : str + add
}

static formatInt (integer, locale) {
try {
return new Intl.NumberFormat(Strings.normalizeLocale(locale), { maximumFractionDigits: 0 }).format(integer)
} catch (_) {}
return String(integer)
}

// valid opts:
// - suffix (string, default based on str) to customize pluralization
// - locale (string, no default) to respect user's language + region
static pluralize (count, noun, opts) {
if (!noun) return ''
noun = String(noun)
if (typeof count !== 'number') count = Number(count)
if (Number.isNaN(count)) count = 0
let suffix, locale
if (typeof opts === 'string') {
suffix = opts
} else {
opts = opts || {}
suffix = opts.suffix
locale = opts.locale
}
return Strings.formatInt(count, locale) + ' ' + (count !== 1 ? Strings.toPlural(noun, { suffix, locale }) : noun)
}

static abbreviate (str) {
if (!str) return ''
return String(str).split(/\s/).reduce((abbr, word) => abbr + word.slice(0, 1), '')
}

// valid opts: plural (boolean), count (number), locale (string), lc (boolean), uc (boolean), abbrev (boolean)
// valid opts:
// - plural (boolean, no default) or count (number, no default) to determine whether singular or plural value should be used
// - suffix (string, default based on value) to customize auto-pluralization
// - lc (boolean, default false) or uc (boolean, default false) to transform value to lower or upper case before returning
// - abbrev (boolean, default false) to abbreviate the value before returning
// - strict (boolean, default true) to return empty string if key not in strings nor in DEFAULTS
// - locale (string, no default) to respect user's language + region
static get (strings, key, opts) {
// allow first two args to be interchangeable
if (typeof strings === 'string') {
Expand All @@ -66,24 +106,27 @@ class Strings {
if (typeof opts === 'boolean') opts = { plural: opts }
else if (typeof opts === 'number') opts = { count: opts }
else opts = opts || {}
// check for locale
const locale = opts.locale || strings.locale
if (typeof opts.strict !== 'boolean') opts.strict = true
// get value (object or singular string)
let val = typeof strings.strings === 'object' ? strings.strings[key] : strings[key]
if (val == null) val = Strings.DEFAULTS[key]

// if (val == null) val = key
if (val == null) return ''
if (val == null) {
if (opts.strict) return ''
val = key
}

// check for locale
const locale = opts.locale || strings.locale
// determine plurality
let usePlural = false
if (typeof opts.plural === 'boolean') usePlural = opts.plural
else if (typeof opts.count === 'number') usePlural = opts.count !== 1
// extract value
if (usePlural) {
if (typeof val === 'string') val = Strings.pluralize(val, locale)
if (typeof val === 'string') val = Strings.toPlural(val, { locale, suffix: opts.suffix })
else if (val.plural || val.other) val = val.plural || val.other
else if (val.singular || val.one) val = Strings.pluralize(val.singular || val.one, locale)
else if (val.singular || val.one) val = Strings.toPlural(val.singular || val.one, { locale, suffix: opts.suffix })
} else if (typeof val !== 'string' && (val.singular || val.one)) {
val = val.singular || val.one
}
Expand All @@ -94,12 +137,21 @@ class Strings {
return opts.abbrev ? Strings.abbreviate(val) : val
}

// valid opts: locale (string), lc (boolean), uc (boolean), abbrev (boolean)
// valid opts:
// - lc (boolean, default false) or uc (boolean, default false) to transform value to lower or upper case before returning
// - abbrev (boolean, default false) to abbreviate the value before returning
// - strict (boolean, default true) to return empty string if key not in strings nor in DEFAULTS
// - locale (string, no default) to respect user's language + region
static getSingular (strings, key, opts) {
return Strings.get(strings, key, Object.assign({}, opts, { plural: false }))
}

// valid opts: locale (string), lc (boolean), uc (boolean), abbrev (boolean)
// valid opts:
// - suffix (string, default based on value) to customize auto-pluralization
// - lc (boolean, default false) or uc (boolean, default false) to transform value to lower or upper case before returning
// - abbrev (boolean, default false) to abbreviate the value before returning
// - strict (boolean, default true) to return empty string if key not in strings nor in DEFAULTS
// - locale (string, no default) to respect user's language + region
static getPlural (strings, key, opts) {
return Strings.get(strings, key, Object.assign({}, opts, { plural: true }))
}
Expand All @@ -112,17 +164,17 @@ class Strings {
this.strings = strings
}

// valid opts: plural (boolean), count (number), locale (string), lc (boolean), uc (boolean), abbrev (boolean)
// valid opts: plural (boolean), count (number), suffix (string), lc (boolean), uc (boolean), abbrev (boolean), strict (boolean), locale (string)
get (key, opts) {
return Strings.get(this.strings, key, opts)
}

// valid opts: locale (string), lc (boolean), uc (boolean), abbrev (boolean)
// valid opts: lc (boolean), uc (boolean), abbrev (boolean), strict (boolean), locale (string)
getSingular (key, opts) {
return Strings.getSingular(this.strings, key, opts)
}

// valid opts: locale (string), lc (boolean), uc (boolean), abbrev (boolean)
// valid opts: suffix (string), lc (boolean), uc (boolean), abbrev (boolean), strict (boolean), locale (string)
getPlural (key, opts) {
return Strings.getPlural(this.strings, key, opts)
}
Expand All @@ -148,6 +200,8 @@ Strings.DISPUTE = 'dispute'
Strings.MEMBER = 'member'
Strings.REP = 'rep'
Strings.TEAM = 'team'
Strings.ADJUSTMENT = 'adjustment'
Strings.BATCH = 'batch'

Strings.DEFAULTS = {
[Strings.GROSS_MARGIN]: {
Expand Down Expand Up @@ -196,7 +250,9 @@ Strings.DEFAULTS = {
[Strings.DISPUTE]: 'Dispute',
[Strings.MEMBER]: 'Member',
[Strings.REP]: 'Rep',
[Strings.TEAM]: 'Team'
[Strings.TEAM]: 'Team',
[Strings.ADJUSTMENT]: 'Adjustment',
[Strings.BATCH]: 'Batch'
}

module.exports = Strings

0 comments on commit 5930c55

Please sign in to comment.