printer.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package locale // import "miniflux.app/v2/internal/locale"
  4. import "fmt"
  5. // Printer converts translation keys to language-specific strings.
  6. type Printer struct {
  7. language string
  8. }
  9. // NewPrinter creates a new Printer instance for the given language.
  10. func NewPrinter(language string) *Printer {
  11. return &Printer{language}
  12. }
  13. func (p *Printer) Print(key string) string {
  14. if dict, err := getTranslationDict(p.language); err == nil {
  15. if str, ok := dict.singulars[key]; ok {
  16. return str
  17. }
  18. }
  19. return key
  20. }
  21. // Printf is like fmt.Printf, but using language-specific formatting.
  22. func (p *Printer) Printf(key string, args ...any) string {
  23. return formatTranslation(p.Print(key), args...)
  24. }
  25. // Plural returns the translation of the given key by using the language plural form.
  26. func (p *Printer) Plural(key string, n int, args ...any) string {
  27. dict, err := getTranslationDict(p.language)
  28. if err != nil {
  29. return key
  30. }
  31. if choices, found := dict.plurals[key]; found {
  32. index := getPluralForm(p.language, n)
  33. if len(choices) > index {
  34. return formatTranslation(choices[index], args...)
  35. }
  36. }
  37. return key
  38. }
  39. // formatTranslation skips extra arguments when the translation references no argument,
  40. // so plural forms that omit the count (e.g. the Arabic dual "دقيقتين") don't get
  41. // a trailing %!(EXTRA ...) marker. Escaped percents are still processed by fmt.
  42. func formatTranslation(format string, args ...any) string {
  43. if !hasFormattingDirective(format) {
  44. return fmt.Sprintf(format, []any{}...)
  45. }
  46. return fmt.Sprintf(format, args...)
  47. }
  48. // hasFormattingDirective reports whether the format should be handled with the
  49. // supplied arguments. It treats "%%" as a literal percent and lets fmt validate
  50. // any other percent sequence, including a dangling "%".
  51. func hasFormattingDirective(format string) bool {
  52. for index := 0; index < len(format); index++ {
  53. if format[index] != '%' {
  54. continue
  55. }
  56. if index+1 >= len(format) {
  57. return true
  58. }
  59. if format[index+1] == '%' {
  60. index++ // skip the escaped percent
  61. continue
  62. }
  63. return true
  64. }
  65. return false
  66. }