github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/i18n/i18n.go (about)

     1  // FIXME: see https://godoc.org/golang.org/x/text/message/catalog and compare
     2  //
     3  // Translation tooling for internationalized sites/pages/components.
     4  //
     5  // Groups and Keys
     6  //
     7  // Groups are logical units of translation, loosely corresponding to an application or Go package.
     8  // Group names can be any Go string but it is recommended that they are alphanumeric and specifically
     9  // should not including whitespace, a colon or other punctuation characters.
    10  //
    11  // Keys are more loose in their requirments and can either be a token or the original language text.
    12  // For example, you can use "title_this_is_a_test" as a key or "This is a Test".  The former is
    13  // more specific and can be used to avoid confusion between the same text appearing in different
    14  // contexts and thus needing different translations; whereas the latter is easier to work with when
    15  // rapidly putting in content since you can avoid providing any translation and the English text
    16  // will still appear correctly and a corresopnding translation file can be created later.  Pick your poison,
    17  // but it's recommended that the larger your project, the more you should consider using surrogate tokens
    18  // for translation keys (i.e. "title_something_here" not "Something Here").
    19  //
    20  // Locales
    21  //
    22  // While no format is strictly enforced for locale names, RFC 3066 and ISO 639 should be used.
    23  // Locale names are treated as case-insensitive (implementations should simply convert to lower case)
    24  // but no other change is performed on locale strings, they are otherwise compared using
    25  // normal string Go equality comparison.  Apply the following rules to avoid confusion:
    26  //
    27  // Always use the shortest possible locale string representation which accurately represents the locale.
    28  // ISO 639.1 has two-letter language codes and ISO 639.2 has three-letter codes - always
    29  // prefer the two-letter code where it exists.  E.g. English is "en" not "eng", Finnish is
    30  // "fi" not "fin", however Filipino is "fil" as there is no ISO 639.1 (two-letter) code for it.
    31  //
    32  // A "subtag" should be used (with a dash) where it is necessary to distinguish between language variations.
    33  // E.g. if you are providing a translation in Spanish you should simply use "es" as the locale.
    34  // However if a Castilian Spanish version is needed for Spain, you can use "es-es" (Spanish - Spain),
    35  // "en-gb" would be used for (English - United Kingdom), and so on.  ISO 3166 two-letter country
    36  // codes should be used (following the "shortest possible representation" concept.)
    37  //
    38  package i18n
    39  
    40  import (
    41  	"log"
    42  
    43  	"github.com/gocaveman/caveman/webutil"
    44  )
    45  
    46  // documentation points:
    47  // - needs to support vastly different sizes of scale from a few dozen or hundred words to large databases of text
    48  // - registration and override mechanism so translations can be plugged in
    49  // - organizing translations into groups so different packages can each provide translations in an appropriate namespace
    50  //   and avoid collisions.
    51  // - the option to use regular English or other default language text as the key or a surrogate identifier, e.g. you can
    52  //   use either "This is a test." or "title_this_is_a_test" as the key.  Each has its pros and cons - using default text
    53  //   is faster to prototype with because you don't need to maintain a translations file just to see the first language;
    54  //   whereas using a surrogate unique id/token ensures translations are specific and individual labels for example don't
    55  //   lose their context.  (Using "Name" as a key could be very confusing for example because it may be the same in English
    56  //   but different in other languages depending on the context.)
    57  // - string replacements (give example)
    58  // - multiple or custom storage format, files or database or other arbitrary
    59  // - default behavior for how pages can easily be translated, but also customizable by replacing out just the logic of
    60  //   what decides the locale for a page (put name of struct or method here)
    61  // - support for determining what locales a page is translated into
    62  // - editor for visual translations ui, with pluggable storage mechanisms (i.e can write to file or db)
    63  // - a way to debug and see where text is being pulled from (although it is quite verbose and only appropriate
    64  //   during debugging)
    65  
    66  // flat file -> sqlite would be an excellent choice here (although it is actually simpler and
    67  // complex queries are not required, so... we'll see - possibly in the case of large datasets,
    68  // but then the build time would be bad.  yeah, simple in memory cache with LRU or something
    69  // if it gets too big - at least the possibility of plugging that in - probably more the way to go)
    70  
    71  // probably want to provide something in the context that can know what the current page's
    72  // locale is, with defaults, and a way to override
    73  
    74  // maybe a registry mechanism so other things can provide translations
    75  
    76  // at least think through what happens if they want to have one page per translation
    77  // instead of using string lookups, we should facilitate that (although doing it
    78  // with the metadata maybe tricky - but give that some thought too);dont' need to die
    79  // over it but it needs to be feasible if desired.
    80  
    81  //////////// AHHHHHHHH - need to have good support for string replacement/arguments - Go templating is probably
    82  // a good choice here, but context needs to be figured out.
    83  
    84  // groups also need more thought out - in cases where we do {{$t.T "Stores"}} is this supposed to have an implied group?
    85  // check it check them all?  should it be the "default" group?  Or should we just ignore groups altogether and treat it
    86  // as a flat keyspace
    87  
    88  var ErrNotFound = webutil.ErrNotFound
    89  
    90  type LocaleGroupTranslator interface {
    91  	T(s string) string // Returns the text translated into one of the target locales or returns back the same text provided as-is.
    92  }
    93  
    94  // LocaleTranslator is aware of the current list of locales for the given situation (HTTP request or other)
    95  // and can translate strings into the appropriate text.
    96  type LocaleTranslator interface {
    97  	T2(g, s string) string // Returns the text translated into one of the target locales or returns back the same text provided as-is.
    98  }
    99  
   100  type DefaultLocaleTranslator struct {
   101  	Locales    []string
   102  	Translator Translator
   103  }
   104  
   105  func (t *DefaultLocaleTranslator) T(g, s string) string {
   106  	ret, err := t.Translator.Translate(g, s, t.Locales...)
   107  	if err != nil {
   108  		log.Printf("Error calling Translator.Translate(%q): %v", s, err)
   109  	}
   110  	return ret
   111  }