You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

96 lines
3.0 KiB

  1. // Package english provides utilities to generate more user-friendly English output.
  2. package english
  3. import (
  4. "fmt"
  5. "strings"
  6. )
  7. // These are included because they are common technical terms.
  8. var specialPlurals = map[string]string{
  9. "index": "indices",
  10. "matrix": "matrices",
  11. "vertex": "vertices",
  12. }
  13. var sibilantEndings = []string{"s", "sh", "tch", "x"}
  14. var isVowel = map[byte]bool{
  15. 'A': true, 'E': true, 'I': true, 'O': true, 'U': true,
  16. 'a': true, 'e': true, 'i': true, 'o': true, 'u': true,
  17. }
  18. // PluralWord builds the plural form of an English word.
  19. // The simple English rules of regular pluralization will be used
  20. // if the plural form is an empty string (i.e. not explicitly given).
  21. // The special cases are not guaranteed to work for strings outside ASCII.
  22. func PluralWord(quantity int, singular, plural string) string {
  23. if quantity == 1 {
  24. return singular
  25. }
  26. if plural != "" {
  27. return plural
  28. }
  29. if plural = specialPlurals[singular]; plural != "" {
  30. return plural
  31. }
  32. // We need to guess what the English plural might be. Keep this
  33. // function simple! It doesn't need to know about every possiblity;
  34. // only regular rules and the most common special cases.
  35. //
  36. // Reference: http://en.wikipedia.org/wiki/English_plural
  37. for _, ending := range sibilantEndings {
  38. if strings.HasSuffix(singular, ending) {
  39. return singular + "es"
  40. }
  41. }
  42. l := len(singular)
  43. if l >= 2 && singular[l-1] == 'o' && !isVowel[singular[l-2]] {
  44. return singular + "es"
  45. }
  46. if l >= 2 && singular[l-1] == 'y' && !isVowel[singular[l-2]] {
  47. return singular[:l-1] + "ies"
  48. }
  49. return singular + "s"
  50. }
  51. // Plural formats an integer and a string into a single pluralized string.
  52. // The simple English rules of regular pluralization will be used
  53. // if the plural form is an empty string (i.e. not explicitly given).
  54. func Plural(quantity int, singular, plural string) string {
  55. return fmt.Sprintf("%d %s", quantity, PluralWord(quantity, singular, plural))
  56. }
  57. // WordSeries converts a list of words into a word series in English.
  58. // It returns a string containing all the given words separated by commas,
  59. // the coordinating conjunction, and a serial comma, as appropriate.
  60. func WordSeries(words []string, conjunction string) string {
  61. switch len(words) {
  62. case 0:
  63. return ""
  64. case 1:
  65. return words[0]
  66. default:
  67. return fmt.Sprintf("%s %s %s", strings.Join(words[:len(words)-1], ", "), conjunction, words[len(words)-1])
  68. }
  69. }
  70. // OxfordWordSeries converts a list of words into a word series in English,
  71. // using an Oxford comma (https://en.wikipedia.org/wiki/Serial_comma). It
  72. // returns a string containing all the given words separated by commas, the
  73. // coordinating conjunction, and a serial comma, as appropriate.
  74. func OxfordWordSeries(words []string, conjunction string) string {
  75. switch len(words) {
  76. case 0:
  77. return ""
  78. case 1:
  79. return words[0]
  80. case 2:
  81. return strings.Join(words, fmt.Sprintf(" %s ", conjunction))
  82. default:
  83. return fmt.Sprintf("%s, %s %s", strings.Join(words[:len(words)-1], ", "), conjunction, words[len(words)-1])
  84. }
  85. }