-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: join strings with a conjunction in a handful of languages
- Loading branch information
1 parent
1cb11ef
commit 83c7316
Showing
5 changed files
with
291 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/charmbracelet/x/exp/strings | ||
|
||
go 1.20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package strings | ||
|
||
// This package works well for some Western languages. PRs for other languages | ||
// are welcome, but do note that implementation for some languages will be less | ||
// straightforward than the ones in use here. | ||
|
||
import ( | ||
"strings" | ||
) | ||
|
||
// Language is a spoken Language. | ||
type Language int | ||
|
||
// Available spoken lanaguges. | ||
const ( | ||
DE Language = iota | ||
DK | ||
EN | ||
ES | ||
FR | ||
IT | ||
NO | ||
PT | ||
SE | ||
) | ||
|
||
// String returns the English name of the [Language] code. | ||
func (l Language) String() string { | ||
return map[Language]string{ | ||
DE: "German", | ||
DK: "Danish", | ||
EN: "English", | ||
ES: "Spanish", | ||
FR: "French", | ||
IT: "Italian", | ||
NO: "Norwegian", | ||
PT: "Portuguese", | ||
SE: "Swedish", | ||
}[l] | ||
} | ||
|
||
func (l Language) conjuction() string { | ||
switch l { | ||
case DE: | ||
return "und" | ||
case DK: | ||
return "og" | ||
case EN: | ||
return "and" | ||
case ES: | ||
return "y" | ||
case FR: | ||
return "et" | ||
case NO: | ||
return "og" | ||
case IT: | ||
return "e" | ||
case PT: | ||
return "e" | ||
case SE: | ||
return "och" | ||
default: | ||
return "" | ||
} | ||
} | ||
|
||
func (l Language) separator() string { | ||
switch l { | ||
case DE, DK, EN, ES, FR, NO, IT, PT, SE: | ||
return ", " | ||
default: | ||
return " " | ||
} | ||
} | ||
|
||
// EnglishJoin joins a slice of strings with commas and the "and" conjugation | ||
// before the final item. The Oxford comma can optionally be applied. | ||
// | ||
// Example: | ||
// | ||
// str := EnglishJoin([]string{"meow", "purr", raow"}, EN, true) | ||
// fmt.Println(str) // meow, purr, and raow | ||
func EnglishJoin(words []string, oxfordComma bool) string { | ||
return spokenLangJoin(words, EN, oxfordComma) | ||
} | ||
|
||
// SpokenLangaugeJoin joins a slice of strings with commas and a conjuction | ||
// before the final item. You may specify the language with [Language]. | ||
// | ||
// If you are using English and need the Oxford Comma, use [EnglishJoin]. | ||
// | ||
// Example: | ||
// | ||
// str := SpokenLanguageJoin([]string{"eins", "zwei", drei"}, DE) | ||
// fmt.Println(str) // eins, zwei und drei | ||
func SpokenLanguageJoin(words []string, language Language) string { | ||
return spokenLangJoin(words, language, false) | ||
} | ||
|
||
func spokenLangJoin(words []string, language Language, oxfordComma bool) string { | ||
conjuction := language.conjuction() + " " | ||
separator := language.separator() | ||
|
||
b := strings.Builder{} | ||
for i, word := range words { | ||
if word == "" { | ||
continue | ||
} | ||
|
||
if i == 0 { | ||
b.WriteString(word) | ||
continue | ||
} | ||
|
||
// Is this the final word? | ||
if len(words) > 1 && i == len(words)-1 { | ||
// Apply the Oxford comma if requested as long as the language is | ||
// English. | ||
if language == EN && oxfordComma && i > 1 { | ||
b.WriteString(separator) | ||
} else { | ||
b.WriteRune(' ') | ||
} | ||
|
||
b.WriteString(conjuction + word) | ||
continue | ||
} | ||
|
||
b.WriteString(separator + word) | ||
} | ||
return b.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package strings | ||
|
||
import "testing" | ||
|
||
func TestEnglishJoin(t *testing.T) { | ||
for i, tc := range []struct { | ||
words []string | ||
lang Language | ||
oxfordComma bool | ||
expected string | ||
}{ | ||
{ | ||
words: []string{"one", "two", "three"}, | ||
lang: EN, | ||
oxfordComma: true, | ||
expected: "one, two, and three", | ||
}, | ||
{ | ||
words: []string{"one", "two", "three", "four"}, | ||
oxfordComma: true, | ||
expected: "one, two, three, and four", | ||
}, | ||
{ | ||
words: []string{"one", "two"}, | ||
oxfordComma: true, | ||
expected: "one and two", | ||
}, | ||
{ | ||
words: []string{"one", "two"}, | ||
oxfordComma: true, | ||
expected: "one and two", | ||
}, | ||
{ | ||
words: []string{"one", "two", "three"}, | ||
oxfordComma: false, | ||
expected: "one, two and three", | ||
}, | ||
{ | ||
words: []string{"one"}, | ||
oxfordComma: true, | ||
expected: "one", | ||
}, | ||
} { | ||
actual := EnglishJoin(tc.words, tc.oxfordComma) | ||
if actual != tc.expected { | ||
t.Errorf("Test #%d:\n expected: %q\n got: %q", i+1, tc.expected, actual) | ||
} | ||
} | ||
} | ||
|
||
func TestSpokenLanguageJoin(t *testing.T) { | ||
for i, tc := range []struct { | ||
words []string | ||
lang Language | ||
expected string | ||
}{ | ||
// Test for correct commas and conjunctions in each language. | ||
{ | ||
words: []string{"eins", "zwei", "drei"}, | ||
lang: DE, | ||
expected: "eins, zwei und drei", | ||
}, | ||
{ | ||
words: []string{"en", "to", "tre"}, | ||
lang: DK, | ||
expected: "en, to og tre", | ||
}, | ||
{ | ||
words: []string{"one", "two", "three"}, | ||
lang: EN, | ||
expected: "one, two and three", | ||
}, | ||
{ | ||
words: []string{"uno", "dos", "tres"}, | ||
lang: ES, | ||
expected: "uno, dos y tres", | ||
}, | ||
{ | ||
words: []string{"un", "deux", "trois"}, | ||
lang: FR, | ||
expected: "un, deux et trois", | ||
}, | ||
{ | ||
words: []string{"uno", "due", "tre"}, | ||
lang: IT, | ||
expected: "uno, due e tre", | ||
}, | ||
{ | ||
words: []string{"en", "to", "tre"}, | ||
lang: NO, | ||
expected: "en, to og tre", | ||
}, | ||
{ | ||
words: []string{"um", "dois", "três"}, | ||
lang: PT, | ||
expected: "um, dois e três", | ||
}, | ||
{ | ||
words: []string{"ett", "två", "tre", "fyra"}, | ||
lang: SE, | ||
expected: "ett, två, tre och fyra", | ||
}, | ||
|
||
// Test other things. | ||
{ | ||
words: []string{"one", "two", "three", "four"}, | ||
lang: EN, | ||
expected: "one, two, three and four", | ||
}, | ||
{ | ||
words: []string{"um", "dois"}, | ||
lang: PT, | ||
expected: "um e dois", | ||
}, | ||
{ | ||
words: []string{"un", "deux"}, | ||
lang: FR, | ||
expected: "un et deux", | ||
}, | ||
{ | ||
words: []string{"one"}, | ||
lang: EN, | ||
expected: "one", | ||
}, | ||
{ | ||
words: []string{}, | ||
lang: EN, | ||
expected: "", | ||
}, | ||
{ | ||
words: []string{"", "", ""}, | ||
lang: EN, | ||
expected: "", | ||
}, | ||
{ | ||
words: nil, | ||
lang: EN, | ||
expected: "", | ||
}, | ||
} { | ||
actual := SpokenLanguageJoin(tc.words, tc.lang) | ||
if actual != tc.expected { | ||
t.Errorf("Test #%d:\n expected: %q\n got: %q", i+1, tc.expected, actual) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,5 +4,6 @@ use ( | |
./exp/higherorder | ||
./exp/ordered | ||
./exp/slice | ||
./exp/strings | ||
./exp/teatest | ||
) |