├── go-i18n ├── goi18n │ ├── testdata │ │ ├── input │ │ │ ├── fr-FR.json │ │ │ ├── en-US.two.json │ │ │ ├── en-US.one.json │ │ │ ├── ar-AR.one.json │ │ │ └── ar-AR.two.json │ │ ├── expected │ │ │ ├── en-US.untranslated.json │ │ │ ├── fr-FR.all.json │ │ │ ├── en-US.all.json │ │ │ ├── fr-FR.untranslated.json │ │ │ ├── ar-AR.all.json │ │ │ └── ar-AR.untranslated.json │ │ └── en-US.yaml │ ├── gendoc.sh │ ├── merge_test.go │ ├── doc.go │ ├── goi18n.go │ └── merge.go ├── i18n │ ├── translation │ │ ├── translation_test.go │ │ ├── single_translation.go │ │ ├── template.go │ │ ├── plural_translation.go │ │ ├── translation.go │ │ ├── template_test.go │ │ └── plural_translation_test.go │ ├── plural │ │ ├── plural_test.go │ │ ├── plural.go │ │ ├── operands_test.go │ │ └── operands.go │ ├── locale │ │ ├── locale_test.go │ │ └── locale.go │ ├── exampletemplate_test.go │ ├── example_test.go │ ├── bundle │ │ ├── bundle_test.go │ │ └── bundle.go │ ├── i18n.go │ └── language │ │ ├── language.go │ │ └── language_test.go ├── LICENSE └── README.md ├── conf └── app.conf ├── views ├── buoy │ ├── page-head.html │ ├── modal │ │ └── pv_station-detail.html │ └── content.html └── shared │ ├── header.html │ ├── modal.html │ └── basic-layout.html ├── static ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── css │ ├── style.css │ ├── main.css │ └── bootstrap-select.css └── js │ ├── buoy │ └── buoy.js │ ├── service.js │ └── bootstrap.min.js ├── zscripts ├── run_endpoint_tests.sh ├── run_service_tests.sh ├── runbuild.sh └── runbuild_with_bee.sh ├── .gitignore ├── utilities ├── helper │ ├── constants.go │ └── catch.go └── mongo │ └── mongo.go ├── localize ├── en-US.go └── messages.go ├── routes └── routes.go ├── main.go ├── test ├── endpointTests │ ├── endpointTests.go │ └── buoyEndpoints_test.go └── serviceTests │ ├── buoyService_test.go │ └── serviceTests.go ├── LICENSE ├── services ├── services.go └── buoyService │ └── buoyService.go ├── models └── buoyModels │ └── buoyModels.go ├── README.md └── controllers ├── buoyController.go └── baseController └── baseController.go /go-i18n/goi18n/testdata/input/fr-FR.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/expected/en-US.untranslated.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = Beego-mgo 2 | httpport = 9003 3 | runmode = dev -------------------------------------------------------------------------------- /views/buoy/page-head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinggo/beego-mgo/HEAD/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinggo/beego-mgo/HEAD/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinggo/beego-mgo/HEAD/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /views/shared/header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /zscripts/run_endpoint_tests.sh: -------------------------------------------------------------------------------- 1 | export MGO_HOSTS=ds035428.mongolab.com:35428 2 | export MGO_DATABASE=goinggo 3 | export MGO_USERNAME=guest 4 | export MGO_PASSWORD=welcome 5 | export BUOY_DATABASE=goinggo 6 | 7 | cd $GOPATH/src/github.com/goinggo/beego-mgo/test/endpointTests 8 | go test -v -------------------------------------------------------------------------------- /zscripts/run_service_tests.sh: -------------------------------------------------------------------------------- 1 | export MGO_HOSTS=ds035428.mongolab.com:35428 2 | export MGO_DATABASE=goinggo 3 | export MGO_USERNAME=guest 4 | export MGO_PASSWORD=welcome 5 | export BUOY_DATABASE=goinggo 6 | 7 | cd $GOPATH/src/github.com/goinggo/beego-mgo/test/serviceTests 8 | go test -v -------------------------------------------------------------------------------- /zscripts/runbuild.sh: -------------------------------------------------------------------------------- 1 | export MGO_HOSTS=ds035428.mongolab.com:35428 2 | export MGO_DATABASE=goinggo 3 | export MGO_USERNAME=guest 4 | export MGO_PASSWORD=welcome 5 | export BUOY_DATABASE=goinggo 6 | 7 | cd $GOPATH/src/github.com/goinggo/beego-mgo 8 | go clean -i 9 | go build 10 | 11 | ./beego-mgo -------------------------------------------------------------------------------- /zscripts/runbuild_with_bee.sh: -------------------------------------------------------------------------------- 1 | export MGO_HOSTS=ds035428.mongolab.com:35428 2 | export MGO_DATABASE=goinggo 3 | export MGO_USERNAME=guest 4 | export MGO_PASSWORD=welcome 5 | export BUOY_DATABASE=goinggo 6 | 7 | cd $GOPATH/src/github.com/goinggo/beego-mgo 8 | go clean -i 9 | go build 10 | 11 | bee run watchall -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | beego-mgo 25 | -------------------------------------------------------------------------------- /go-i18n/goi18n/gendoc.sh: -------------------------------------------------------------------------------- 1 | go install 2 | echo "// The goi18n command formats and merges translation files." > doc.go 3 | echo "//" >> doc.go 4 | echo "// go get -u github.com/nicksnyder/go-i18n/goi18n" >> doc.go 5 | echo "// goi18n -help" >> doc.go 6 | echo "//" >> doc.go 7 | echo "// Help documentation:" >> doc.go 8 | echo "//" >> doc.go 9 | goi18n -help | sed -e 's/^/\/\/ /' >> doc.go 10 | echo "package main" >> doc.go 11 | -------------------------------------------------------------------------------- /go-i18n/i18n/translation/translation_test.go: -------------------------------------------------------------------------------- 1 | package translation 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | ) 7 | 8 | // Check this here to avoid unnecessary import of sort package. 9 | var _ = sort.Interface(make(SortableByID, 0, 0)) 10 | 11 | func TestNewSingleTranslation(t *testing.T) { 12 | t.Skipf("not implemented") 13 | } 14 | 15 | func TestNewPluralTranslation(t *testing.T) { 16 | t.Skipf("not implemented") 17 | } 18 | -------------------------------------------------------------------------------- /utilities/helper/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package helper : constants.go implements boilerplate code for the web service. 6 | package helper 7 | 8 | //** CONSTANTS 9 | 10 | const ( 11 | // MainGoRoutine is just a label for logging. 12 | MainGoRoutine = "main" 13 | ) 14 | -------------------------------------------------------------------------------- /localize/en-US.go: -------------------------------------------------------------------------------- 1 | // Package localize : en-US.go provides the localized messages for English in the United States 2 | package localize 3 | 4 | // EnUS contains english United States translations. 5 | var EnUS = `[ 6 | { 7 | "id": "invalid_credentials", 8 | "translation": "Invalid Credentials were supplied." 9 | }, 10 | { 11 | "id": "application_error", 12 | "translation": "An Application Error has occured." 13 | }, 14 | { 15 | "id": "invalid_station_id", 16 | "translation": "Invalid Station Id Or Missing" 17 | } 18 | ]` 19 | -------------------------------------------------------------------------------- /routes/routes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package routes initializes the routes for the web service. 6 | package routes 7 | 8 | import ( 9 | "github.com/astaxie/beego" 10 | "github.com/goinggo/beego-mgo/controllers" 11 | ) 12 | 13 | func init() { 14 | beego.Router("/", new(controllers.BuoyController), "get:Index") 15 | beego.Router("/buoy/retrievestation", new(controllers.BuoyController), "post:RetrieveStation") 16 | beego.Router("/buoy/station/:stationId", new(controllers.BuoyController), "get,post:RetrieveStationJSON") 17 | } 18 | -------------------------------------------------------------------------------- /views/shared/modal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/input/en-US.two.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "person_greeting", 4 | "translation": "Hello {{.Person}}" 5 | }, 6 | { 7 | "id": "person_unread_email_count", 8 | "translation": { 9 | "one": "{{.Person}} has {{.Count}} unread email.", 10 | "other": "{{.Person}} has {{.Count}} unread emails." 11 | } 12 | }, 13 | { 14 | "id": "person_unread_email_count_timeframe", 15 | "translation": { 16 | "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." 17 | } 18 | }, 19 | { 20 | "id": "d_days", 21 | "translation": { 22 | "one": "{{.Count}} day", 23 | "other": "{{.Count}} days" 24 | } 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /utilities/helper/catch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package helper : catch.go implements boilerplate code for the web service. 6 | package helper 7 | 8 | import ( 9 | "fmt" 10 | "runtime" 11 | ) 12 | 13 | // CatchPanic is used to catch any Panic and log exceptions to Stdout. It will also write the stack trace 14 | func CatchPanic(err *error, sessionID string, functionName string) { 15 | if r := recover(); r != nil { 16 | buf := make([]byte, 10000) 17 | runtime.Stack(buf, false) 18 | 19 | if err != nil { 20 | *err = fmt.Errorf("%v", r) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /views/buoy/modal/pv_station-detail.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 11 |
12 |
-------------------------------------------------------------------------------- /go-i18n/i18n/plural/plural_test.go: -------------------------------------------------------------------------------- 1 | package plural 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewCategory(t *testing.T) { 8 | tests := []struct { 9 | src string 10 | cat Category 11 | err bool 12 | }{ 13 | {"zero", Zero, false}, 14 | {"one", One, false}, 15 | {"two", Two, false}, 16 | {"few", Few, false}, 17 | {"many", Many, false}, 18 | {"other", Other, false}, 19 | {"asdf", Invalid, true}, 20 | } 21 | 22 | for _, test := range tests { 23 | cat, err := NewCategory(test.src) 24 | wrongErr := (err != nil && !test.err) || (err == nil && test.err) 25 | if cat != test.cat || wrongErr { 26 | t.Errorf("New(%#v) returned %#v,%#v; expected %#v", test.src, cat, err, test.cat) 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/input/en-US.one.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "program_greeting", 4 | "translation": "Hello world" 5 | }, 6 | { 7 | "id": "your_unread_email_count", 8 | "translation": { 9 | "one": "You have {{.Count}} unread email.", 10 | "other": "You have {{.Count}} unread emails." 11 | } 12 | }, 13 | { 14 | "id": "my_height_in_meters", 15 | "translation": { 16 | "one": "I am {{.Count}} meter tall.", 17 | "other": "I am {{.Count}} meters tall." 18 | } 19 | }, 20 | { 21 | "id": "person_unread_email_count_timeframe", 22 | "translation": { 23 | "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." 24 | } 25 | }, 26 | { 27 | "id": "d_days", 28 | "translation": "this should get overwritten" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/en-US.yaml: -------------------------------------------------------------------------------- 1 | - id: program_greeting 2 | translation: "Hello world" 3 | 4 | - id: person_greeting 5 | translation: "Hello {{.Person}}" 6 | 7 | - id: your_unread_email_count 8 | translations: 9 | one: "You have {{.Count}} unread email." 10 | other: "You have {{.Count}} unread emails." 11 | 12 | - id: person_unread_email_count 13 | translations: 14 | one: "{{.Person}} has {{.Count}} unread email." 15 | other: "{{.Person}} has {{.Count}} unread emails." 16 | 17 | - id: person_unread_email_count_timeframe 18 | translations: 19 | one: "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." 20 | other: "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." 21 | 22 | - id: d_days 23 | translations: 24 | one: "{{.Count}} day" 25 | other: "{{.Count}} days" 26 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/expected/fr-FR.all.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d_days", 4 | "translation": { 5 | "one": "", 6 | "other": "" 7 | } 8 | }, 9 | { 10 | "id": "my_height_in_meters", 11 | "translation": { 12 | "one": "", 13 | "other": "" 14 | } 15 | }, 16 | { 17 | "id": "person_greeting", 18 | "translation": "" 19 | }, 20 | { 21 | "id": "person_unread_email_count", 22 | "translation": { 23 | "one": "", 24 | "other": "" 25 | } 26 | }, 27 | { 28 | "id": "person_unread_email_count_timeframe", 29 | "translation": { 30 | "one": "", 31 | "other": "" 32 | } 33 | }, 34 | { 35 | "id": "program_greeting", 36 | "translation": "" 37 | }, 38 | { 39 | "id": "your_unread_email_count", 40 | "translation": { 41 | "one": "", 42 | "other": "" 43 | } 44 | } 45 | ] -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package main provides sample web application for beego and mgo. 6 | package main 7 | 8 | import ( 9 | "github.com/astaxie/beego" 10 | "github.com/goinggo/beego-mgo/localize" 11 | _ "github.com/goinggo/beego-mgo/routes" 12 | "github.com/goinggo/beego-mgo/utilities/helper" 13 | "github.com/goinggo/beego-mgo/utilities/mongo" 14 | "github.com/goinggo/tracelog" 15 | "os" 16 | ) 17 | 18 | func main() { 19 | tracelog.Start(tracelog.LevelTrace) 20 | 21 | // Init mongo 22 | tracelog.Started("main", "Initializing Mongo") 23 | err := mongo.Startup(helper.MainGoRoutine) 24 | if err != nil { 25 | tracelog.CompletedError(err, helper.MainGoRoutine, "initApp") 26 | os.Exit(1) 27 | } 28 | 29 | // Load message strings 30 | localize.Init("en-US") 31 | 32 | beego.Run() 33 | 34 | tracelog.Completed(helper.MainGoRoutine, "Website Shutdown") 35 | tracelog.Stop() 36 | } 37 | -------------------------------------------------------------------------------- /go-i18n/i18n/plural/plural.go: -------------------------------------------------------------------------------- 1 | // Package plural defines CLDR plural categories. 2 | package plural 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | // Category represents a language pluralization form as defined here: 9 | // http://cldr.unicode.org/index/cldr-spec/plural-rules 10 | type Category string 11 | 12 | // All defined plural categories. 13 | const ( 14 | Invalid Category = "invalid" 15 | Zero = "zero" 16 | One = "one" 17 | Two = "two" 18 | Few = "few" 19 | Many = "many" 20 | Other = "other" 21 | ) 22 | 23 | // NewCategory returns src as a Category 24 | // or Invalid and a non-nil error if src is not a valid Category. 25 | func NewCategory(src string) (Category, error) { 26 | switch src { 27 | case "zero": 28 | return Zero, nil 29 | case "one": 30 | return One, nil 31 | case "two": 32 | return Two, nil 33 | case "few": 34 | return Few, nil 35 | case "many": 36 | return Many, nil 37 | case "other": 38 | return Other, nil 39 | } 40 | return Invalid, fmt.Errorf("invalid plural category %s", src) 41 | } 42 | -------------------------------------------------------------------------------- /test/endpointTests/endpointTests.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package endpointTests implements boilerplate code for all testing. 6 | package endpointTests 7 | 8 | import ( 9 | "github.com/goinggo/beego-mgo/localize" 10 | _ "github.com/goinggo/beego-mgo/routes" // Initalize routes 11 | "github.com/goinggo/beego-mgo/utilities/helper" 12 | "github.com/goinggo/beego-mgo/utilities/mongo" 13 | log "github.com/goinggo/tracelog" 14 | ) 15 | 16 | //** CONSTANTS 17 | 18 | const ( 19 | // SessionID is just mocking the id for testing. 20 | SessionID = "testing" 21 | ) 22 | 23 | //** INIT 24 | 25 | // init initializes all required packages and systems 26 | func init() { 27 | log.Start(log.LevelTrace) 28 | 29 | // Init mongo 30 | log.Started("main", "Initializing Mongo") 31 | err := mongo.Startup(helper.MainGoRoutine) 32 | if err != nil { 33 | log.CompletedError(err, helper.MainGoRoutine, "initTesting") 34 | return 35 | } 36 | 37 | // Load message strings 38 | localize.Init("en-US") 39 | } 40 | -------------------------------------------------------------------------------- /go-i18n/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Nick Snyder https://github.com/nicksnyder 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /go-i18n/i18n/locale/locale_test.go: -------------------------------------------------------------------------------- 1 | package locale 2 | 3 | import ( 4 | "github.com/goinggo/beego-mgo/go-i18n/i18n/language" 5 | "testing" 6 | ) 7 | 8 | func TestNew(t *testing.T) { 9 | tests := []struct { 10 | localeID string 11 | lang *language.Language 12 | }{ 13 | {"en-US", language.LanguageWithID("en")}, 14 | {"en_US", language.LanguageWithID("en")}, 15 | {"zh-CN", language.LanguageWithID("zh")}, 16 | {"zh-TW", language.LanguageWithID("zh")}, 17 | {"pt-BR", language.LanguageWithID("pt-BR")}, 18 | {"pt_BR", language.LanguageWithID("pt-BR")}, 19 | {"pt-PT", language.LanguageWithID("pt")}, 20 | {"pt_PT", language.LanguageWithID("pt")}, 21 | {"zh-Hans-CN", nil}, 22 | {"zh-Hant-TW", nil}, 23 | {"xx-Yyen-US", nil}, 24 | {"en US", nil}, 25 | {"en-US-en-US", nil}, 26 | {".en-US..en-US.", nil}, 27 | } 28 | for _, test := range tests { 29 | loc, err := New(test.localeID) 30 | if loc == nil && test.lang != nil { 31 | t.Errorf("New(%q) = , %q; expected %q, ", test.localeID, err, test.lang) 32 | } 33 | if loc != nil && loc.Language != test.lang { 34 | t.Errorf("New(%q) = %q; expected %q", test.localeID, loc.Language, test.lang) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /go-i18n/i18n/exampletemplate_test.go: -------------------------------------------------------------------------------- 1 | package i18n_test 2 | 3 | import ( 4 | "github.com/goinggo/beego-mgo/go-i18n/i18n" 5 | "os" 6 | "text/template" 7 | ) 8 | 9 | var funcMap = map[string]interface{}{ 10 | "T": i18n.IdentityTfunc, 11 | } 12 | 13 | var tmpl = template.Must(template.New("").Funcs(funcMap).Parse(` 14 | {{T "program_greeting"}} 15 | {{T "person_greeting" .}} 16 | {{T "your_unread_email_count" 0}} 17 | {{T "your_unread_email_count" 1}} 18 | {{T "your_unread_email_count" 2}} 19 | {{T "person_unread_email_count" 0 .}} 20 | {{T "person_unread_email_count" 1 .}} 21 | {{T "person_unread_email_count" 2 .}} 22 | `)) 23 | 24 | func Example_template() { 25 | i18n.MustLoadTranslationFile("../goi18n/testdata/expected/en-US.all.json") 26 | 27 | T, _ := i18n.Tfunc("en-US") 28 | tmpl.Funcs(map[string]interface{}{ 29 | "T": T, 30 | }) 31 | 32 | tmpl.Execute(os.Stdout, map[string]interface{}{ 33 | "Person": "Bob", 34 | "Timeframe": T("d_days", 1), 35 | }) 36 | 37 | // Output: 38 | // Hello world 39 | // Hello Bob 40 | // You have 0 unread emails. 41 | // You have 1 unread email. 42 | // You have 2 unread emails. 43 | // Bob has 0 unread emails. 44 | // Bob has 1 unread email. 45 | // Bob has 2 unread emails. 46 | } 47 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/expected/en-US.all.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d_days", 4 | "translation": { 5 | "one": "{{.Count}} day", 6 | "other": "{{.Count}} days" 7 | } 8 | }, 9 | { 10 | "id": "my_height_in_meters", 11 | "translation": { 12 | "one": "I am {{.Count}} meter tall.", 13 | "other": "I am {{.Count}} meters tall." 14 | } 15 | }, 16 | { 17 | "id": "person_greeting", 18 | "translation": "Hello {{.Person}}" 19 | }, 20 | { 21 | "id": "person_unread_email_count", 22 | "translation": { 23 | "one": "{{.Person}} has {{.Count}} unread email.", 24 | "other": "{{.Person}} has {{.Count}} unread emails." 25 | } 26 | }, 27 | { 28 | "id": "person_unread_email_count_timeframe", 29 | "translation": { 30 | "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", 31 | "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." 32 | } 33 | }, 34 | { 35 | "id": "program_greeting", 36 | "translation": "Hello world" 37 | }, 38 | { 39 | "id": "your_unread_email_count", 40 | "translation": { 41 | "one": "You have {{.Count}} unread email.", 42 | "other": "You have {{.Count}} unread emails." 43 | } 44 | } 45 | ] -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/expected/fr-FR.untranslated.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d_days", 4 | "translation": { 5 | "one": "{{.Count}} days", 6 | "other": "{{.Count}} days" 7 | } 8 | }, 9 | { 10 | "id": "my_height_in_meters", 11 | "translation": { 12 | "one": "I am {{.Count}} meters tall.", 13 | "other": "I am {{.Count}} meters tall." 14 | } 15 | }, 16 | { 17 | "id": "person_greeting", 18 | "translation": "Hello {{.Person}}" 19 | }, 20 | { 21 | "id": "person_unread_email_count", 22 | "translation": { 23 | "one": "{{.Person}} has {{.Count}} unread emails.", 24 | "other": "{{.Person}} has {{.Count}} unread emails." 25 | } 26 | }, 27 | { 28 | "id": "person_unread_email_count_timeframe", 29 | "translation": { 30 | "one": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 31 | "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." 32 | } 33 | }, 34 | { 35 | "id": "program_greeting", 36 | "translation": "Hello world" 37 | }, 38 | { 39 | "id": "your_unread_email_count", 40 | "translation": { 41 | "one": "You have {{.Count}} unread emails.", 42 | "other": "You have {{.Count}} unread emails." 43 | } 44 | } 45 | ] -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/input/ar-AR.one.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d_days", 4 | "translation": { 5 | "few": "arabic few translation of d_days", 6 | "many": "arabic many translation of d_days", 7 | "one": "", 8 | "other": "", 9 | "two": "", 10 | "zero": "" 11 | } 12 | }, 13 | { 14 | "id": "person_greeting", 15 | "translation": "arabic translation of person_greeting" 16 | }, 17 | { 18 | "id": "person_unread_email_count", 19 | "translation": { 20 | "few": "arabic few translation of person_unread_email_count", 21 | "many": "arabic many translation of person_unread_email_count", 22 | "one": "arabic one translation of person_unread_email_count", 23 | "other": "", 24 | "two": "", 25 | "zero": "" 26 | } 27 | }, 28 | { 29 | "id": "person_unread_email_count_timeframe", 30 | "translation": { 31 | "few": "", 32 | "many": "", 33 | "one": "", 34 | "other": "", 35 | "two": "", 36 | "zero": "" 37 | } 38 | }, 39 | { 40 | "id": "program_greeting", 41 | "translation": "" 42 | }, 43 | { 44 | "id": "your_unread_email_count", 45 | "translation": { 46 | "few": "", 47 | "many": "", 48 | "one": "", 49 | "other": "", 50 | "two": "", 51 | "zero": "" 52 | } 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/input/ar-AR.two.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d_days", 4 | "translation": { 5 | "few": "new arabic few translation of d_days", 6 | "many": "", 7 | "one": "arabic one translation of d_days", 8 | "other": "", 9 | "two": "", 10 | "zero": "" 11 | } 12 | }, 13 | { 14 | "id": "person_greeting", 15 | "translation": "new arabic translation of person_greeting" 16 | }, 17 | { 18 | "id": "person_unread_email_count", 19 | "translation": { 20 | "few": "", 21 | "many": "", 22 | "one": "", 23 | "other": "arabic other translation of person_unread_email_count", 24 | "two": "arabic two translation of person_unread_email_count", 25 | "zero": "arabic zero translation of person_unread_email_count" 26 | } 27 | }, 28 | { 29 | "id": "person_unread_email_count_timeframe", 30 | "translation": { 31 | "few": "", 32 | "many": "", 33 | "one": "", 34 | "other": "", 35 | "two": "", 36 | "zero": "" 37 | } 38 | }, 39 | { 40 | "id": "program_greeting", 41 | "translation": "" 42 | }, 43 | { 44 | "id": "your_unread_email_count", 45 | "translation": { 46 | "few": "", 47 | "many": "", 48 | "one": "", 49 | "other": "", 50 | "two": "", 51 | "zero": "" 52 | } 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /go-i18n/i18n/plural/operands_test.go: -------------------------------------------------------------------------------- 1 | package plural 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNewOperands(t *testing.T) { 9 | tests := []struct { 10 | input interface{} 11 | ops *Operands 12 | err bool 13 | }{ 14 | {int64(0), &Operands{0.0, 0, 0, 0, 0, 0}, false}, 15 | {int64(1), &Operands{1.0, 1, 0, 0, 0, 0}, false}, 16 | {"0", &Operands{0.0, 0, 0, 0, 0, 0}, false}, 17 | {"1", &Operands{1.0, 1, 0, 0, 0, 0}, false}, 18 | {"1.0", &Operands{1.0, 1, 1, 0, 0, 0}, false}, 19 | {"1.00", &Operands{1.0, 1, 2, 0, 0, 0}, false}, 20 | {"1.3", &Operands{1.3, 1, 1, 1, 3, 3}, false}, 21 | {"1.30", &Operands{1.3, 1, 2, 1, 30, 3}, false}, 22 | {"1.03", &Operands{1.03, 1, 2, 2, 3, 3}, false}, 23 | {"1.230", &Operands{1.23, 1, 3, 2, 230, 23}, false}, 24 | {"20.0230", &Operands{20.023, 20, 4, 3, 230, 23}, false}, 25 | {20.0230, nil, true}, 26 | } 27 | for _, test := range tests { 28 | ops, err := NewOperands(test.input) 29 | if err != nil && !test.err { 30 | t.Errorf("NewOperands(%#v) unexpected error: %s", test.input, err) 31 | } else if err == nil && test.err { 32 | t.Errorf("NewOperands(%#v) returned %#v; expected error", test.input, ops) 33 | } else if !reflect.DeepEqual(ops, test.ops) { 34 | t.Errorf("NewOperands(%#v) returned %#v; expected %#v", test.input, ops, test.ops) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkNewOperand(b *testing.B) { 40 | for i := 0; i < b.N; i++ { 41 | if _, err := NewOperands("1234.56780000"); err != nil { 42 | b.Fatal(err) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/serviceTests/buoyService_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package serviceTests implements tests for the buoy services. 6 | package serviceTests 7 | 8 | import ( 9 | "testing" 10 | 11 | "github.com/goinggo/beego-mgo/services/buoyService" 12 | . "github.com/smartystreets/goconvey/convey" 13 | ) 14 | 15 | // Test_Station checks the station service call is working 16 | func Test_Station(t *testing.T) { 17 | service := Prepare() 18 | defer Finish(service) 19 | 20 | stationID := "42002" 21 | 22 | buoyStation, err := buoyService.FindStation(service, stationID) 23 | 24 | Convey("Subject: Test Station Service", t, func() { 25 | Convey("Should Be Able To Perform A Search", func() { 26 | So(err, ShouldEqual, nil) 27 | }) 28 | Convey("Should Have Station Data", func() { 29 | So(buoyStation.StationID, ShouldEqual, stationID) 30 | }) 31 | }) 32 | } 33 | 34 | // Test_Region checks the region service call is working 35 | func Test_Region(t *testing.T) { 36 | service := Prepare() 37 | defer Finish(service) 38 | 39 | region := "Gulf Of Mexico" 40 | 41 | buoyStations, err := buoyService.FindRegion(service, region) 42 | 43 | Convey("Subject: Test Region Service", t, func() { 44 | Convey("Should Be Able To Perform A Search", func() { 45 | So(err, ShouldEqual, nil) 46 | }) 47 | Convey("Should Have Region Data", func() { 48 | So(len(buoyStations), ShouldBeGreaterThan, 0) 49 | }) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /go-i18n/i18n/translation/single_translation.go: -------------------------------------------------------------------------------- 1 | package translation 2 | 3 | import ( 4 | "github.com/goinggo/beego-mgo/go-i18n/i18n/language" 5 | "github.com/goinggo/beego-mgo/go-i18n/i18n/plural" 6 | ) 7 | 8 | type singleTranslation struct { 9 | id string 10 | template *template 11 | } 12 | 13 | func (st *singleTranslation) MarshalInterface() interface{} { 14 | return map[string]interface{}{ 15 | "id": st.id, 16 | "translation": st.template, 17 | } 18 | } 19 | 20 | func (st *singleTranslation) ID() string { 21 | return st.id 22 | } 23 | 24 | func (st *singleTranslation) Template(pc plural.Category) *template { 25 | return st.template 26 | } 27 | 28 | func (st *singleTranslation) UntranslatedCopy() Translation { 29 | return &singleTranslation{st.id, mustNewTemplate("")} 30 | } 31 | 32 | func (st *singleTranslation) Normalize(language *language.Language) Translation { 33 | return st 34 | } 35 | 36 | func (st *singleTranslation) Backfill(src Translation) Translation { 37 | if st.template == nil || st.template.src == "" { 38 | st.template = src.Template(plural.Other) 39 | } 40 | return st 41 | } 42 | 43 | func (st *singleTranslation) Merge(t Translation) Translation { 44 | other, ok := t.(*singleTranslation) 45 | if !ok || st.ID() != t.ID() { 46 | return t 47 | } 48 | if other.template != nil && other.template.src != "" { 49 | st.template = other.template 50 | } 51 | return st 52 | } 53 | 54 | func (st *singleTranslation) Incomplete(l *language.Language) bool { 55 | return st.template == nil || st.template.src == "" 56 | } 57 | 58 | var _ = Translation(&singleTranslation{}) 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Simplified BSD License 2 | 3 | Copyright (c) 2013, Ardan Studios 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | The views and conclusions contained in the software and documentation are those 27 | of the authors and should not be interpreted as representing official policies, 28 | either expressed or implied, of the FreeBSD Project. -------------------------------------------------------------------------------- /services/services.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of service source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package services implements boilerplate code for all services. 6 | package services 7 | 8 | import ( 9 | "github.com/goinggo/beego-mgo/utilities/helper" 10 | "github.com/goinggo/beego-mgo/utilities/mongo" 11 | log "github.com/goinggo/tracelog" 12 | "gopkg.in/mgo.v2" 13 | ) 14 | 15 | //** TYPES 16 | 17 | type ( 18 | // Service contains common properties for all services. 19 | Service struct { 20 | MongoSession *mgo.Session 21 | UserID string 22 | } 23 | ) 24 | 25 | //** PUBLIC FUNCTIONS 26 | 27 | // Prepare is called before any controller. 28 | func (service *Service) Prepare() (err error) { 29 | service.MongoSession, err = mongo.CopyMonotonicSession(service.UserID) 30 | if err != nil { 31 | log.Error(err, service.UserID, "Service.Prepare") 32 | return err 33 | } 34 | 35 | return err 36 | } 37 | 38 | // Finish is called after the controller. 39 | func (service *Service) Finish() (err error) { 40 | defer helper.CatchPanic(&err, service.UserID, "Service.Finish") 41 | 42 | if service.MongoSession != nil { 43 | mongo.CloseSession(service.UserID, service.MongoSession) 44 | service.MongoSession = nil 45 | } 46 | 47 | return err 48 | } 49 | 50 | // DBAction executes the MongoDB literal function 51 | func (service *Service) DBAction(databaseName string, collectionName string, dbCall mongo.DBCall) (err error) { 52 | return mongo.Execute(service.UserID, service.MongoSession, databaseName, collectionName, dbCall) 53 | } 54 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/expected/ar-AR.all.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d_days", 4 | "translation": { 5 | "few": "new arabic few translation of d_days", 6 | "many": "arabic many translation of d_days", 7 | "one": "arabic one translation of d_days", 8 | "other": "", 9 | "two": "", 10 | "zero": "" 11 | } 12 | }, 13 | { 14 | "id": "my_height_in_meters", 15 | "translation": { 16 | "few": "", 17 | "many": "", 18 | "one": "", 19 | "other": "", 20 | "two": "", 21 | "zero": "" 22 | } 23 | }, 24 | { 25 | "id": "person_greeting", 26 | "translation": "new arabic translation of person_greeting" 27 | }, 28 | { 29 | "id": "person_unread_email_count", 30 | "translation": { 31 | "few": "arabic few translation of person_unread_email_count", 32 | "many": "arabic many translation of person_unread_email_count", 33 | "one": "arabic one translation of person_unread_email_count", 34 | "other": "arabic other translation of person_unread_email_count", 35 | "two": "arabic two translation of person_unread_email_count", 36 | "zero": "arabic zero translation of person_unread_email_count" 37 | } 38 | }, 39 | { 40 | "id": "person_unread_email_count_timeframe", 41 | "translation": { 42 | "few": "", 43 | "many": "", 44 | "one": "", 45 | "other": "", 46 | "two": "", 47 | "zero": "" 48 | } 49 | }, 50 | { 51 | "id": "program_greeting", 52 | "translation": "" 53 | }, 54 | { 55 | "id": "your_unread_email_count", 56 | "translation": { 57 | "few": "", 58 | "many": "", 59 | "one": "", 60 | "other": "", 61 | "two": "", 62 | "zero": "" 63 | } 64 | } 65 | ] -------------------------------------------------------------------------------- /go-i18n/i18n/translation/template.go: -------------------------------------------------------------------------------- 1 | package translation 2 | 3 | import ( 4 | "bytes" 5 | "encoding" 6 | "strings" 7 | //"launchpad.net/goyaml" 8 | gotemplate "text/template" 9 | ) 10 | 11 | type template struct { 12 | tmpl *gotemplate.Template 13 | src string 14 | } 15 | 16 | func newTemplate(src string) (*template, error) { 17 | var tmpl template 18 | err := tmpl.parseTemplate(src) 19 | return &tmpl, err 20 | } 21 | 22 | func mustNewTemplate(src string) *template { 23 | t, err := newTemplate(src) 24 | if err != nil { 25 | panic(err) 26 | } 27 | return t 28 | } 29 | 30 | func (t *template) String() string { 31 | return t.src 32 | } 33 | 34 | func (t *template) Execute(args interface{}) string { 35 | if t.tmpl == nil { 36 | return t.src 37 | } 38 | var buf bytes.Buffer 39 | if err := t.tmpl.Execute(&buf, args); err != nil { 40 | return err.Error() 41 | } 42 | return buf.String() 43 | } 44 | 45 | func (t *template) MarshalText() ([]byte, error) { 46 | return []byte(t.src), nil 47 | } 48 | 49 | func (t *template) UnmarshalText(src []byte) error { 50 | return t.parseTemplate(string(src)) 51 | } 52 | 53 | func (t *template) parseTemplate(src string) (err error) { 54 | t.src = src 55 | if strings.Contains(src, "{{") { 56 | t.tmpl, err = gotemplate.New(src).Parse(src) 57 | } 58 | return 59 | } 60 | 61 | var _ = encoding.TextMarshaler(&template{}) 62 | var _ = encoding.TextUnmarshaler(&template{}) 63 | 64 | /* 65 | func (t *template) GetYAML() (tag string, value interface{}) { 66 | return "", t.src 67 | } 68 | 69 | func (t *template) SetYAML(tag string, value interface{}) bool { 70 | panic(tag) 71 | src, ok := value.(string) 72 | if !ok { 73 | return false 74 | } 75 | return t.parseTemplate(src) == nil 76 | } 77 | 78 | var _ = goyaml.Getter(&template{}) 79 | var _ = goyaml.Setter(&template{}) 80 | */ 81 | -------------------------------------------------------------------------------- /models/buoyModels/buoyModels.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package buoyModels contains the models for the buoy service. 6 | package buoyModels 7 | 8 | import ( 9 | "fmt" 10 | 11 | "gopkg.in/mgo.v2/bson" 12 | ) 13 | 14 | //** TYPES 15 | 16 | type ( 17 | // BuoyCondition contains information for an individual station. 18 | BuoyCondition struct { 19 | WindSpeed float64 `bson:"wind_speed_milehour" json:"wind_speed_milehour"` 20 | WindDirection int `bson:"wind_direction_degnorth" json:"wind_direction_degnorth"` 21 | WindGust float64 `bson:"gust_wind_speed_milehour" json:"gust_wind_speed_milehour"` 22 | } 23 | 24 | // BuoyLocation contains the buoys location. 25 | BuoyLocation struct { 26 | Type string `bson:"type" json:"type"` 27 | Coordinates []float64 `bson:"coordinates" json:"coordinates"` 28 | } 29 | 30 | // BuoyStation contains information for an individual station. 31 | BuoyStation struct { 32 | ID bson.ObjectId `bson:"_id,omitempty"` 33 | StationID string `bson:"station_id" json:"station_id"` 34 | Name string `bson:"name" json:"name"` 35 | LocDesc string `bson:"location_desc" json:"location_desc"` 36 | Condition BuoyCondition `bson:"condition" json:"condition"` 37 | Location BuoyLocation `bson:"location" json:"location"` 38 | } 39 | ) 40 | 41 | // DisplayWindSpeed pretty prints wind speed. 42 | func (buoyCondition *BuoyCondition) DisplayWindSpeed() string { 43 | return fmt.Sprintf("%.2f", buoyCondition.WindSpeed) 44 | } 45 | 46 | // DisplayWindGust pretty prints wind gust. 47 | func (buoyCondition *BuoyCondition) DisplayWindGust() string { 48 | return fmt.Sprintf("%.2f", buoyCondition.WindGust) 49 | } 50 | -------------------------------------------------------------------------------- /views/shared/basic-layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sample Beego App - {{.Title}} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{.PageHead}} 18 | 19 | 20 | 21 | 25 | 26 | 36 | 37 | 38 | {{.Header}} 39 | 40 | {{.LayoutContent}} 41 | {{.Modal}} 42 | 43 | -------------------------------------------------------------------------------- /go-i18n/i18n/example_test.go: -------------------------------------------------------------------------------- 1 | package i18n_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goinggo/beego-mgo/go-i18n/i18n" 6 | ) 7 | 8 | func Example() { 9 | i18n.MustLoadTranslationFile("../goi18n/testdata/expected/en-US.all.json") 10 | 11 | T, _ := i18n.Tfunc("en-US") 12 | 13 | fmt.Println(T("program_greeting")) 14 | fmt.Println(T("person_greeting", map[string]interface{}{ 15 | "Person": "Bob", 16 | })) 17 | 18 | fmt.Println(T("your_unread_email_count", 0)) 19 | fmt.Println(T("your_unread_email_count", 1)) 20 | fmt.Println(T("your_unread_email_count", 2)) 21 | fmt.Println(T("my_height_in_meters", "1.7")) 22 | 23 | fmt.Println(T("person_unread_email_count", 0, map[string]interface{}{ 24 | "Person": "Bob", 25 | })) 26 | fmt.Println(T("person_unread_email_count", 1, map[string]interface{}{ 27 | "Person": "Bob", 28 | })) 29 | fmt.Println(T("person_unread_email_count", 2, map[string]interface{}{ 30 | "Person": "Bob", 31 | })) 32 | 33 | fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ 34 | "Person": "Bob", 35 | "Timeframe": T("d_days", 0), 36 | })) 37 | fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ 38 | "Person": "Bob", 39 | "Timeframe": T("d_days", 1), 40 | })) 41 | fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ 42 | "Person": "Bob", 43 | "Timeframe": T("d_days", 2), 44 | })) 45 | 46 | // Output: 47 | // Hello world 48 | // Hello Bob 49 | // You have 0 unread emails. 50 | // You have 1 unread email. 51 | // You have 2 unread emails. 52 | // I am 1.7 meters tall. 53 | // Bob has 0 unread emails. 54 | // Bob has 1 unread email. 55 | // Bob has 2 unread emails. 56 | // Bob has 3 unread emails in the past 0 days. 57 | // Bob has 3 unread emails in the past 1 day. 58 | // Bob has 3 unread emails in the past 2 days. 59 | } 60 | -------------------------------------------------------------------------------- /go-i18n/goi18n/merge_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestMergeExecute(t *testing.T) { 11 | resetDir(t, "testdata/output") 12 | files := []string{ 13 | "testdata/input/en-US.one.json", 14 | "testdata/input/en-US.two.json", 15 | "testdata/input/fr-FR.json", 16 | "testdata/input/ar-AR.one.json", 17 | "testdata/input/ar-AR.two.json", 18 | } 19 | 20 | mc := &mergeCommand{ 21 | translationFiles: files, 22 | sourceLocaleID: "en-US", 23 | outdir: "testdata/output", 24 | format: "json", 25 | } 26 | if err := mc.execute(); err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | expectEqualFiles(t, "testdata/output/en-US.all.json", "testdata/expected/en-US.all.json") 31 | expectEqualFiles(t, "testdata/output/ar-AR.all.json", "testdata/expected/ar-AR.all.json") 32 | expectEqualFiles(t, "testdata/output/fr-FR.all.json", "testdata/expected/fr-FR.all.json") 33 | expectEqualFiles(t, "testdata/output/en-US.untranslated.json", "testdata/expected/en-US.untranslated.json") 34 | expectEqualFiles(t, "testdata/output/ar-AR.untranslated.json", "testdata/expected/ar-AR.untranslated.json") 35 | expectEqualFiles(t, "testdata/output/fr-FR.untranslated.json", "testdata/expected/fr-FR.untranslated.json") 36 | } 37 | 38 | func resetDir(t *testing.T, dir string) { 39 | if err := os.RemoveAll(dir); err != nil { 40 | t.Fatal(err) 41 | } 42 | if err := os.Mkdir(dir, 0777); err != nil { 43 | t.Fatal(err) 44 | } 45 | } 46 | 47 | func expectEqualFiles(t *testing.T, expectedName, actualName string) { 48 | actual, err := ioutil.ReadFile(actualName) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | expected, err := ioutil.ReadFile(expectedName) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | if !bytes.Equal(actual, expected) { 57 | t.Fatalf("contents of files did not match: %s, %s", expectedName, actualName) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /go-i18n/i18n/locale/locale.go: -------------------------------------------------------------------------------- 1 | // Package locale parses locale strings. 2 | package locale 3 | 4 | import ( 5 | "fmt" 6 | "github.com/goinggo/beego-mgo/go-i18n/i18n/language" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | // Locale is a language and a geographic region (e.g. en-US, en-GB). 12 | type Locale struct { 13 | ID string 14 | Language *language.Language 15 | } 16 | 17 | // tagMatcher matches language tags (e.g. zh-CN). 18 | var tagMatcher = regexp.MustCompile(`^([a-z]{2})[_\-]([A-Z]{2})$`) 19 | 20 | // tagSplitter matches characters not found in language tags. 21 | var tagSplitter = regexp.MustCompile(`[^a-zA-Z_\-]+`) 22 | 23 | // New searches s for a valid language tag (RFC 5646) 24 | // of the form xx-YY or xx_YY where 25 | // xx is a 2 character language code and 26 | // YY is a 2 character country code. 27 | // 28 | // It returns an error if s doesn't contain exactly one language tag or 29 | // if the language represented by the tag is not supported by this package. 30 | func New(s string) (*Locale, error) { 31 | parts := tagSplitter.Split(s, -1) 32 | var id, lc string 33 | count := 0 34 | for _, part := range parts { 35 | if tag := tagMatcher.FindStringSubmatch(part); tag != nil { 36 | count += 1 37 | id, lc = tag[0], tag[1] 38 | } 39 | } 40 | if count != 1 { 41 | return nil, fmt.Errorf("%d locales found in string %s", count, s) 42 | } 43 | id = strings.Replace(id, "_", "-", -1) 44 | lang := language.LanguageWithID(id) 45 | if lang == nil { 46 | lang = language.LanguageWithID(lc) 47 | } 48 | if lang == nil { 49 | return nil, fmt.Errorf("unknown language %s", id) 50 | } 51 | return &Locale{id, lang}, nil 52 | } 53 | 54 | // MustNew is similar to New except that it panics if an error happens. 55 | func MustNew(s string) *Locale { 56 | locale, err := New(s) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return locale 61 | } 62 | 63 | func (l *Locale) String() string { 64 | return l.ID 65 | } 66 | -------------------------------------------------------------------------------- /go-i18n/goi18n/testdata/expected/ar-AR.untranslated.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d_days", 4 | "translation": { 5 | "few": "new arabic few translation of d_days", 6 | "many": "arabic many translation of d_days", 7 | "one": "arabic one translation of d_days", 8 | "other": "{{.Count}} days", 9 | "two": "{{.Count}} days", 10 | "zero": "{{.Count}} days" 11 | } 12 | }, 13 | { 14 | "id": "my_height_in_meters", 15 | "translation": { 16 | "few": "I am {{.Count}} meters tall.", 17 | "many": "I am {{.Count}} meters tall.", 18 | "one": "I am {{.Count}} meters tall.", 19 | "other": "I am {{.Count}} meters tall.", 20 | "two": "I am {{.Count}} meters tall.", 21 | "zero": "I am {{.Count}} meters tall." 22 | } 23 | }, 24 | { 25 | "id": "person_unread_email_count_timeframe", 26 | "translation": { 27 | "few": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 28 | "many": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 29 | "one": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 30 | "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 31 | "two": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 32 | "zero": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." 33 | } 34 | }, 35 | { 36 | "id": "program_greeting", 37 | "translation": "Hello world" 38 | }, 39 | { 40 | "id": "your_unread_email_count", 41 | "translation": { 42 | "few": "You have {{.Count}} unread emails.", 43 | "many": "You have {{.Count}} unread emails.", 44 | "one": "You have {{.Count}} unread emails.", 45 | "other": "You have {{.Count}} unread emails.", 46 | "two": "You have {{.Count}} unread emails.", 47 | "zero": "You have {{.Count}} unread emails." 48 | } 49 | } 50 | ] -------------------------------------------------------------------------------- /test/serviceTests/serviceTests.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package serviceTests implements boilerplate code for all testing 6 | package serviceTests 7 | 8 | import ( 9 | "github.com/goinggo/beego-mgo/localize" 10 | "github.com/goinggo/beego-mgo/services" 11 | "github.com/goinggo/beego-mgo/utilities/helper" 12 | "github.com/goinggo/beego-mgo/utilities/mongo" 13 | log "github.com/goinggo/tracelog" 14 | ) 15 | 16 | //** CONSTANTS 17 | 18 | const ( 19 | // SessionID is just mocking the id for testing. 20 | SessionID = "testing" 21 | ) 22 | 23 | //** TYPES 24 | 25 | type ( 26 | // testController contains state and behavior for testing 27 | testController struct { 28 | services.Service 29 | } 30 | ) 31 | 32 | //** INIT 33 | 34 | // init initializes all required packages and systems 35 | func init() { 36 | log.Start(log.LEVEL_TRACE) 37 | 38 | // Init mongo 39 | log.Started("main", "Initializing Mongo") 40 | err := mongo.Startup(helper.MainGoRoutine) 41 | if err != nil { 42 | log.CompletedError(err, helper.MainGoRoutine, "initTesting") 43 | return 44 | } 45 | 46 | // Load message strings 47 | localize.Init("en-US") 48 | } 49 | 50 | //** INTERCEPT FUNCTIONS 51 | 52 | // Prepare is called before controllers are called. 53 | func Prepare() *services.Service { 54 | var service services.Service 55 | 56 | // TODO: Add Test User To Environment 57 | service.UserID = "testing" 58 | 59 | err := service.Prepare() 60 | if err != nil { 61 | log.Error(err, service.UserID, "Prepare") 62 | return nil 63 | } 64 | 65 | log.Trace(service.UserID, "Before", "UserID[%s]", service.UserID) 66 | return &service 67 | } 68 | 69 | // Finish is called after controllers are called. 70 | func Finish(service *services.Service) { 71 | service.Finish() 72 | 73 | log.Completed(service.UserID, "Finish") 74 | } 75 | -------------------------------------------------------------------------------- /views/buoy/content.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 9 |
10 |
11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 |

This examples shows how to load a view, use a partial view and a modal dialog.

19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {{range $index, $val := .Stations}} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {{end}} 39 |
Station IDNameLocDescWind SpeedWind DirectionWind Gust
{{$val.StationID}}{{$val.Name}}{{$val.LocDesc}}{{$val.Condition.DisplayWindSpeed}}{{$val.Condition.WindDirection}}{{$val.Condition.DisplayWindGust}}
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |

This example shows how to return a JSON document.

48 |
49 | 54 | 55 |

56 |
57 | Loading View, Please Wait... 58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 | -------------------------------------------------------------------------------- /go-i18n/i18n/translation/plural_translation.go: -------------------------------------------------------------------------------- 1 | package translation 2 | 3 | import ( 4 | "github.com/goinggo/beego-mgo/go-i18n/i18n/language" 5 | "github.com/goinggo/beego-mgo/go-i18n/i18n/plural" 6 | ) 7 | 8 | type pluralTranslation struct { 9 | id string 10 | templates map[plural.Category]*template 11 | } 12 | 13 | func (pt *pluralTranslation) MarshalInterface() interface{} { 14 | return map[string]interface{}{ 15 | "id": pt.id, 16 | "translation": pt.templates, 17 | } 18 | } 19 | 20 | func (pt *pluralTranslation) ID() string { 21 | return pt.id 22 | } 23 | 24 | func (pt *pluralTranslation) Template(pc plural.Category) *template { 25 | return pt.templates[pc] 26 | } 27 | 28 | func (pt *pluralTranslation) UntranslatedCopy() Translation { 29 | return &pluralTranslation{pt.id, make(map[plural.Category]*template)} 30 | } 31 | 32 | func (pt *pluralTranslation) Normalize(l *language.Language) Translation { 33 | // Delete plural categories that don't belong to this language. 34 | for pc := range pt.templates { 35 | if _, ok := l.PluralCategories[pc]; !ok { 36 | delete(pt.templates, pc) 37 | } 38 | } 39 | // Create map entries for missing valid categories. 40 | for pc := range l.PluralCategories { 41 | if _, ok := pt.templates[pc]; !ok { 42 | pt.templates[pc] = mustNewTemplate("") 43 | } 44 | } 45 | return pt 46 | } 47 | 48 | func (pt *pluralTranslation) Backfill(src Translation) Translation { 49 | for pc, t := range pt.templates { 50 | if t == nil || t.src == "" { 51 | pt.templates[pc] = src.Template(plural.Other) 52 | } 53 | } 54 | return pt 55 | } 56 | 57 | func (pt *pluralTranslation) Merge(t Translation) Translation { 58 | other, ok := t.(*pluralTranslation) 59 | if !ok || pt.ID() != t.ID() { 60 | return t 61 | } 62 | for pluralCategory, template := range other.templates { 63 | if template != nil && template.src != "" { 64 | pt.templates[pluralCategory] = template 65 | } 66 | } 67 | return pt 68 | } 69 | 70 | func (pt *pluralTranslation) Incomplete(l *language.Language) bool { 71 | for pc := range l.PluralCategories { 72 | if t := pt.templates[pc]; t == nil || t.src == "" { 73 | return true 74 | } 75 | } 76 | return false 77 | } 78 | 79 | var _ = Translation(&pluralTranslation{}) 80 | -------------------------------------------------------------------------------- /go-i18n/i18n/plural/operands.go: -------------------------------------------------------------------------------- 1 | package plural 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // http://unicode.org/reports/tr35/tr35-numbers.html#Operands 10 | type Operands struct { 11 | N float64 // absolute value of the source number (integer and decimals) 12 | I int64 // integer digits of n 13 | V int // number of visible fraction digits in n, with trailing zeros 14 | W int // number of visible fraction digits in n, without trailing zeros 15 | F int // visible fractional digits in n, with trailing zeros 16 | T int // visible fractional digits in n, without trailing zeros 17 | } 18 | 19 | func NewOperands(v interface{}) (*Operands, error) { 20 | switch v := v.(type) { 21 | case int: 22 | return newOperandsInt64(int64(v)), nil 23 | case int8: 24 | return newOperandsInt64(int64(v)), nil 25 | case int16: 26 | return newOperandsInt64(int64(v)), nil 27 | case int32: 28 | return newOperandsInt64(int64(v)), nil 29 | case int64: 30 | return newOperandsInt64(v), nil 31 | case string: 32 | return newOperandsString(v) 33 | case float32, float64: 34 | return nil, fmt.Errorf("floats should be formatted into a string") 35 | default: 36 | return nil, fmt.Errorf("invalid type %T; expected integer or string", v) 37 | } 38 | } 39 | 40 | func newOperandsInt64(i int64) *Operands { 41 | if i < 0 { 42 | i = -i 43 | } 44 | return &Operands{float64(i), i, 0, 0, 0, 0} 45 | } 46 | 47 | func newOperandsString(s string) (*Operands, error) { 48 | if s[0] == '-' { 49 | s = s[1:] 50 | } 51 | n, err := strconv.ParseFloat(s, 64) 52 | if err != nil { 53 | return nil, err 54 | } 55 | ops := &Operands{N: n} 56 | parts := strings.SplitN(s, ".", 2) 57 | ops.I, err = strconv.ParseInt(parts[0], 10, 64) 58 | if err != nil { 59 | return nil, err 60 | } 61 | if len(parts) == 1 { 62 | return ops, nil 63 | } 64 | fraction := parts[1] 65 | ops.V = len(fraction) 66 | for i := ops.V - 1; i >= 0; i-- { 67 | if fraction[i] != '0' { 68 | ops.W = i + 1 69 | break 70 | } 71 | } 72 | if ops.V > 0 { 73 | f, err := strconv.ParseInt(fraction, 10, 0) 74 | if err != nil { 75 | return nil, err 76 | } 77 | ops.F = int(f) 78 | } 79 | if ops.W > 0 { 80 | t, err := strconv.ParseInt(fraction[:ops.W], 10, 0) 81 | if err != nil { 82 | return nil, err 83 | } 84 | ops.T = int(t) 85 | } 86 | return ops, nil 87 | } 88 | -------------------------------------------------------------------------------- /go-i18n/goi18n/doc.go: -------------------------------------------------------------------------------- 1 | // The goi18n command formats and merges translation files. 2 | // 3 | // go get -u github.com/finapps/go-i18n/goi18n 4 | // goi18n -help 5 | // 6 | // Help documentation: 7 | // 8 | // goi18n formats and merges translation files. 9 | // 10 | // Usage: 11 | // 12 | // goi18n [options] [files...] 13 | // 14 | // Translation files: 15 | // 16 | // A translation file contains the strings and translations for a single locale (language + country). 17 | // 18 | // Translation file names must have a suffix of a supported format (e.g. .json) and 19 | // contain a valid locale identifier (e.g. ar-EG, en-US, fr-FR, etc.). 20 | // 21 | // For each locale represented by at least one input translation file, goi18n will produce 2 output files: 22 | // 23 | // xx-XX.all.format 24 | // This file contains all strings for the locale (translated and untranslated). 25 | // 26 | // xx-XX.untranslated.format 27 | // This file contains the strings that have not been translated for this locale. 28 | // The translations for the strings in this file will be extracted from the source locale. 29 | // Get these strings translated! After they are translated, merge them back into 30 | // xx-XX.all.format using goi18n. 31 | // 32 | // goi18n will merge multiple translation files for the same locale. 33 | // Duplicate translations will be merged into the existing translation. 34 | // Non-empty fields in the duplicate translation will overwrite those fields in the existing translation. 35 | // Empty fields in the duplicate translation are ignored. 36 | // 37 | // To produce translation files for a new locale, create an empty translation file with the 38 | // appropriate name and pass it in to goi18n. 39 | // 40 | // Options: 41 | // 42 | // -sourceLocale localeId 43 | // The id of the locale that strings are initially written in (e.g. xx-XX) 44 | // Default: en-US 45 | // 46 | // -outdir directory 47 | // goi18n will write the output translation files to this directory. 48 | // Default: . 49 | // 50 | // -format format 51 | // goi18n will encode the output translation files in this format. 52 | // Supported formats: json 53 | // Default: json 54 | // 55 | package main 56 | -------------------------------------------------------------------------------- /go-i18n/i18n/translation/translation.go: -------------------------------------------------------------------------------- 1 | // Package translation defines the interface for a translation. 2 | package translation 3 | 4 | import ( 5 | "fmt" 6 | "github.com/goinggo/beego-mgo/go-i18n/i18n/language" 7 | "github.com/goinggo/beego-mgo/go-i18n/i18n/plural" 8 | ) 9 | 10 | // Translation is the interface that represents a translated string. 11 | type Translation interface { 12 | // MarshalInterface returns the object that should be used 13 | // to serialize the translation. 14 | MarshalInterface() interface{} 15 | ID() string 16 | Template(plural.Category) *template 17 | UntranslatedCopy() Translation 18 | Normalize(language *language.Language) Translation 19 | Backfill(src Translation) Translation 20 | Merge(Translation) Translation 21 | Incomplete(l *language.Language) bool 22 | } 23 | 24 | // SortableByID implements sort.Interface for a slice of translations. 25 | type SortableByID []Translation 26 | 27 | func (a SortableByID) Len() int { return len(a) } 28 | func (a SortableByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 29 | func (a SortableByID) Less(i, j int) bool { return a[i].ID() < a[j].ID() } 30 | 31 | // NewTranslation reflects on data to create a new Translation. 32 | // 33 | // data["id"] must be a string and data["translation"] must be either a string 34 | // for a non-plural translation or a map[string]interface{} for a plural translation. 35 | func NewTranslation(data map[string]interface{}) (Translation, error) { 36 | id, ok := data["id"].(string) 37 | if !ok { 38 | return nil, fmt.Errorf(`missing "id" key`) 39 | } 40 | switch translation := data["translation"].(type) { 41 | case string: 42 | tmpl, err := newTemplate(translation) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return &singleTranslation{id, tmpl}, nil 47 | case map[string]interface{}: 48 | templates := make(map[plural.Category]*template, len(translation)) 49 | for k, v := range translation { 50 | pc, err := plural.NewCategory(k) 51 | if err != nil { 52 | return nil, err 53 | } 54 | str, ok := v.(string) 55 | if !ok { 56 | return nil, fmt.Errorf(`plural category "%s" has value of type %T; expected string`, pc, v) 57 | } 58 | tmpl, err := newTemplate(str) 59 | if err != nil { 60 | return nil, err 61 | } 62 | templates[pc] = tmpl 63 | } 64 | return &pluralTranslation{id, templates}, nil 65 | case nil: 66 | return nil, fmt.Errorf(`missing "translation" key`) 67 | default: 68 | return nil, fmt.Errorf(`unsupported type for "translation" key %T`, translation) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Beego Mgo Example 2 | 3 | Copyright 2013 Ardan Studios. All rights reserved. 4 | Use of this source code is governed by a BSD-style license that can be found in the LICENSE handle. 5 | 6 | This application provides a sample to use the beego web framework and the Go MongoDB driver mgo. This program connects to a public MongoDB at MongoLab. A single collection is available for testing. 7 | 8 | The project includes several shell scripts in the zscripts folder to make building, running and testing the web application easier. 9 | 10 | GoingGo.net Post: 11 | http://www.goinggo.net/2013/12/sample-web-application-using-beego-and.html 12 | 13 | Ardan Studios 14 | 12973 SW 112 ST, Suite 153 15 | Miami, FL 33186 16 | bill@ardanstudios.com 17 | 18 | ### Installation 19 | 20 | -- YOU MUST HAVE BAZAAR INSTALLED 21 | http://wiki.bazaar.canonical.com/Download 22 | 23 | -- Get, build and install the code 24 | go get github.com/goinggo/beego-mgo 25 | 26 | -- Run the web service 27 | cd $GOPATH/src/github.com/goinggo/beego-mgo/zscripts 28 | ./runbuild.sh 29 | 30 | -- Run the tests 31 | cd $GOPATH/src/github.com/goinggo/beego-mgo/zscripts 32 | ./runtests.sh 33 | 34 | -- Test Web Service API's 35 | Run the home page and go through the tabs 36 | http://localhost:9003 37 | 38 | ### Notes About Architecture 39 | 40 | I have been asked why I have organized the code in this way? 41 | 42 | The models folder contains the data structures for the individual services. Each service places their models in a separate folder. 43 | 44 | The services folder contain the raw service calls that the business layer would use to implement higher level functionality. 45 | 46 | The controller methods handle and process the requests. 47 | 48 | The more that can be abstracted into the base controller and base service the better. This way, adding a new functionality is simple and you don't need to worry about forgetting to do something important. Authentication always comes to mind. 49 | 50 | The utilities folder is just that, support for the web application, mostly used by the services. You have exception handling support, extended logging support and the mongo support. 51 | 52 | The abstraction layer for executing MongoDB queries and commands help hide the boilerplate code away into the base service and mongo utility code. 53 | 54 | Using environmental variables for the configuration parameters provides a best practice for minimizing security risks. The scripts in the zscripts folder contains the environment variables required to run the web application. In a real project these settings would never be saved in source control. 55 | -------------------------------------------------------------------------------- /go-i18n/goi18n/goi18n.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func usage() { 10 | fmt.Printf(`goi18n formats and merges translation files. 11 | 12 | Usage: 13 | 14 | goi18n [options] [files...] 15 | 16 | Translation files: 17 | 18 | A translation file contains the strings and translations for a single locale (language + country). 19 | 20 | Translation file names must have a suffix of a supported format (e.g. .json) and 21 | contain a valid locale identifier (e.g. ar-EG, en-US, fr-FR, etc.). 22 | 23 | For each locale represented by at least one input translation file, goi18n will produce 2 output files: 24 | 25 | xx-XX.all.format 26 | This file contains all strings for the locale (translated and untranslated). 27 | 28 | xx-XX.untranslated.format 29 | This file contains the strings that have not been translated for this locale. 30 | The translations for the strings in this file will be extracted from the source locale. 31 | Get these strings translated! After they are translated, merge them back into 32 | xx-XX.all.format using goi18n. 33 | 34 | goi18n will merge multiple translation files for the same locale. 35 | Duplicate translations will be merged into the existing translation. 36 | Non-empty fields in the duplicate translation will overwrite those fields in the existing translation. 37 | Empty fields in the duplicate translation are ignored. 38 | 39 | To produce translation files for a new locale, create an empty translation file with the 40 | appropriate name and pass it in to goi18n. 41 | 42 | Options: 43 | 44 | -sourceLocale localeId 45 | The id of the locale that strings are initially written in (e.g. xx-XX) 46 | Default: en-US 47 | 48 | -outdir directory 49 | goi18n will write the output translation files to this directory. 50 | Default: . 51 | 52 | -format format 53 | goi18n will encode the output translation files in this format. 54 | Supported formats: json 55 | Default: json 56 | 57 | `) 58 | os.Exit(1) 59 | } 60 | 61 | func main() { 62 | flag.Usage = usage 63 | sourceLocale := flag.String("sourceLocale", "en-US", "") 64 | outdir := flag.String("outdir", ".", "") 65 | format := flag.String("format", "json", "") 66 | flag.Parse() 67 | 68 | mc := &mergeCommand{ 69 | translationFiles: flag.Args(), 70 | sourceLocaleID: *sourceLocale, 71 | outdir: *outdir, 72 | format: *format, 73 | } 74 | if err := mc.execute(); err != nil { 75 | fmt.Println(err.Error()) 76 | os.Exit(1) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /static/js/buoy/buoy.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('.detail').click(function(e) { 3 | e.preventDefault(); 4 | ShowDetail(this); 5 | }); 6 | 7 | $('#station-names-json').change(function() { 8 | LoadStationJson(); 9 | }); 10 | 11 | $('#load-station-button-json').click(function() { 12 | LoadStationJsonOwnTab(); 13 | }); 14 | 15 | LoadStationJson(); 16 | }); 17 | 18 | function Standard_Callback() { 19 | try { 20 | alert(this.ResultString); 21 | } 22 | 23 | catch (e) { 24 | alert(e); 25 | } 26 | } 27 | 28 | function Standard_ValidationCallback() { 29 | try { 30 | alert(this.ResultString); 31 | } 32 | 33 | catch (e) { 34 | alert(e); 35 | } 36 | } 37 | 38 | function Standard_ErrorCallback() { 39 | try { 40 | alert(this.ResultString); 41 | } 42 | 43 | catch (e) { 44 | alert(e); 45 | } 46 | } 47 | 48 | function ShowDetail(result) { 49 | try { 50 | var postData = {}; 51 | postData["stationId"] = $(result).attr('data'); 52 | 53 | var service = new ServiceResult(); 54 | service.getJSONData("/buoy/retrievestation", 55 | postData, 56 | ShowDetail_Callback, 57 | Standard_ValidationCallback, 58 | Standard_ErrorCallback 59 | ); 60 | } 61 | 62 | catch (e) { 63 | alert(e); 64 | } 65 | } 66 | 67 | function ShowDetail_Callback() { 68 | try { 69 | $('#system-modal-title').html("Buoy Details"); 70 | $('#system-modal-content').html(this.ResultObject); 71 | $("#systemModal").modal('show'); 72 | } 73 | 74 | catch (e) { 75 | alert(e); 76 | } 77 | } 78 | 79 | function LoadStationJson() { 80 | try { 81 | $('#stations-view').html('Loading View, Please Wait...'); 82 | 83 | url = "/buoy/station/" + $('#station-names-json').val(); 84 | 85 | var postData = {}; 86 | 87 | var service = new ServiceResult(); 88 | service.getJSONDataRaw(url, 89 | postData, 90 | LoadStationJson_Callback 91 | ); 92 | } 93 | 94 | catch (e) { 95 | alert(e); 96 | } 97 | } 98 | 99 | function LoadStationJson_Callback() { 100 | try { 101 | $('#stations-view-json').html(JSON.stringify(this.Data)); 102 | } 103 | 104 | catch (e) { 105 | alert(e); 106 | } 107 | } 108 | 109 | function LoadStationJsonOwnTab() { 110 | url = "/buoy/station/" + $('#station-names-json').val(); 111 | window.open(url); 112 | } 113 | -------------------------------------------------------------------------------- /services/buoyService/buoyService.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package buoyService implements the service for the buoy functionality. 6 | package buoyService 7 | 8 | import ( 9 | "github.com/goinggo/beego-mgo/models/buoyModels" 10 | "github.com/goinggo/beego-mgo/services" 11 | "github.com/goinggo/beego-mgo/utilities/helper" 12 | "github.com/goinggo/beego-mgo/utilities/mongo" 13 | log "github.com/goinggo/tracelog" 14 | "github.com/kelseyhightower/envconfig" 15 | "gopkg.in/mgo.v2" 16 | "gopkg.in/mgo.v2/bson" 17 | ) 18 | 19 | //** TYPES 20 | 21 | type ( 22 | // buoyConfiguration contains settings for running the buoy service. 23 | buoyConfiguration struct { 24 | Database string 25 | } 26 | ) 27 | 28 | //** PACKAGE VARIABLES 29 | 30 | // Config provides buoy configuration. 31 | var Config buoyConfiguration 32 | 33 | //** INIT 34 | 35 | func init() { 36 | // Pull in the configuration. 37 | if err := envconfig.Process("buoy", &Config); err != nil { 38 | log.CompletedError(err, helper.MainGoRoutine, "Init") 39 | } 40 | } 41 | 42 | //** PUBLIC FUNCTIONS 43 | 44 | // FindStation retrieves the specified station 45 | func FindStation(service *services.Service, stationID string) (*buoyModels.BuoyStation, error) { 46 | log.Startedf(service.UserID, "FindStation", "stationID[%s]", stationID) 47 | 48 | var buoyStation buoyModels.BuoyStation 49 | f := func(collection *mgo.Collection) error { 50 | queryMap := bson.M{"station_id": stationID} 51 | 52 | log.Trace(service.UserID, "FindStation", "MGO : db.buoy_stations.find(%s).limit(1)", mongo.ToString(queryMap)) 53 | return collection.Find(queryMap).One(&buoyStation) 54 | } 55 | 56 | if err := service.DBAction(Config.Database, "buoy_stations", f); err != nil { 57 | if err != mgo.ErrNotFound { 58 | log.CompletedError(err, service.UserID, "FindStation") 59 | return nil, err 60 | } 61 | } 62 | 63 | log.Completedf(service.UserID, "FindStation", "buoyStation%+v", &buoyStation) 64 | return &buoyStation, nil 65 | } 66 | 67 | // FindRegion retrieves the stations for the specified region 68 | func FindRegion(service *services.Service, region string) ([]buoyModels.BuoyStation, error) { 69 | log.Startedf(service.UserID, "FindRegion", "region[%s]", region) 70 | 71 | var buoyStations []buoyModels.BuoyStation 72 | f := func(collection *mgo.Collection) error { 73 | queryMap := bson.M{"region": region} 74 | 75 | log.Trace(service.UserID, "FindRegion", "Query : db.buoy_stations.find(%s)", mongo.ToString(queryMap)) 76 | return collection.Find(queryMap).All(&buoyStations) 77 | } 78 | 79 | if err := service.DBAction(Config.Database, "buoy_stations", f); err != nil { 80 | log.CompletedError(err, service.UserID, "FindRegion") 81 | return nil, err 82 | } 83 | 84 | log.Completedf(service.UserID, "FindRegion", "buoyStations%+v", buoyStations) 85 | return buoyStations, nil 86 | } 87 | -------------------------------------------------------------------------------- /controllers/buoyController.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of controller source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package controllers implements the controller layer for the buoy API. 6 | package controllers 7 | 8 | import ( 9 | bc "github.com/goinggo/beego-mgo/controllers/baseController" 10 | "github.com/goinggo/beego-mgo/services/buoyService" 11 | log "github.com/goinggo/tracelog" 12 | ) 13 | 14 | //** TYPES 15 | 16 | // BuoyController manages the API for buoy related functionality. 17 | type BuoyController struct { 18 | bc.BaseController 19 | } 20 | 21 | //** WEB FUNCTIONS 22 | 23 | // Index is the initial view for the buoy system. 24 | func (controller *BuoyController) Index() { 25 | region := "Gulf Of Mexico" 26 | log.Startedf(controller.UserID, "BuoyController.Index", "Region[%s]", region) 27 | 28 | buoyStations, err := buoyService.FindRegion(&controller.Service, region) 29 | if err != nil { 30 | log.CompletedErrorf(err, controller.UserID, "BuoyController.Index", "Region[%s]", region) 31 | controller.ServeError(err) 32 | return 33 | } 34 | 35 | controller.Data["Stations"] = buoyStations 36 | controller.Layout = "shared/basic-layout.html" 37 | controller.TplNames = "buoy/content.html" 38 | controller.LayoutSections = map[string]string{} 39 | controller.LayoutSections["PageHead"] = "buoy/page-head.html" 40 | controller.LayoutSections["Header"] = "shared/header.html" 41 | controller.LayoutSections["Modal"] = "shared/modal.html" 42 | } 43 | 44 | //** AJAX FUNCTIONS 45 | 46 | // RetrieveStation handles the example 2 tab. 47 | func (controller *BuoyController) RetrieveStation() { 48 | var params struct { 49 | StationID string `form:"stationID" valid:"Required; MinSize(4)" error:"invalid_station_id"` 50 | } 51 | 52 | if controller.ParseAndValidate(¶ms) == false { 53 | return 54 | } 55 | 56 | buoyStation, err := buoyService.FindStation(&controller.Service, params.StationID) 57 | if err != nil { 58 | log.CompletedErrorf(err, controller.UserID, "BuoyController.RetrieveStation", "StationID[%s]", params.StationID) 59 | controller.ServeError(err) 60 | return 61 | } 62 | 63 | controller.Data["Station"] = buoyStation 64 | controller.Layout = "" 65 | controller.TplNames = "buoy/modal/pv_station-detail.html" 66 | view, _ := controller.RenderString() 67 | 68 | controller.AjaxResponse(0, "SUCCESS", view) 69 | } 70 | 71 | // RetrieveStationJSON handles the example 3 tab. 72 | // http://localhost:9003/buoy/station/42002 73 | func (controller *BuoyController) RetrieveStationJSON() { 74 | // The call to ParseForm inside of ParseAndValidate is failing. This is a BAD FIX 75 | params := struct { 76 | StationID string `form:":stationId" valid:"Required; MinSize(4)" error:"invalid_station_id"` 77 | }{controller.GetString(":stationId")} 78 | 79 | if controller.ParseAndValidate(¶ms) == false { 80 | return 81 | } 82 | 83 | buoyStation, err := buoyService.FindStation(&controller.Service, params.StationID) 84 | if err != nil { 85 | log.CompletedErrorf(err, controller.UserID, "Station", "StationID[%s]", params.StationID) 86 | controller.ServeError(err) 87 | return 88 | } 89 | 90 | controller.Data["json"] = buoyStation 91 | controller.ServeJson() 92 | } 93 | -------------------------------------------------------------------------------- /go-i18n/i18n/bundle/bundle_test.go: -------------------------------------------------------------------------------- 1 | package bundle 2 | 3 | import ( 4 | "github.com/goinggo/beego-mgo/go-i18n/i18n/locale" 5 | "github.com/goinggo/beego-mgo/go-i18n/i18n/translation" 6 | "testing" 7 | ) 8 | 9 | func TestMustLoadTranslationFile(t *testing.T) { 10 | t.Skipf("not implemented") 11 | } 12 | 13 | func TestLoadTranslationFile(t *testing.T) { 14 | t.Skipf("not implemented") 15 | } 16 | 17 | func TestAddTranslation(t *testing.T) { 18 | t.Skipf("not implemented") 19 | } 20 | 21 | func TestMustTfunc(t *testing.T) { 22 | defer func() { 23 | if r := recover(); r == nil { 24 | t.Errorf("expected MustTfunc to panic") 25 | } 26 | }() 27 | New().MustTfunc("invalid") 28 | } 29 | 30 | func TestTfunc(t *testing.T) { 31 | b := New() 32 | translationID := "translation_id" 33 | englishTranslation := "en-US(translation_id)" 34 | b.AddTranslation(locale.MustNew("en-US"), testNewTranslation(t, map[string]interface{}{ 35 | "id": translationID, 36 | "translation": englishTranslation, 37 | })) 38 | frenchTranslation := "fr-FR(translation_id)" 39 | b.AddTranslation(locale.MustNew("fr-FR"), testNewTranslation(t, map[string]interface{}{ 40 | "id": translationID, 41 | "translation": frenchTranslation, 42 | })) 43 | 44 | tests := []struct { 45 | localeIDs []string 46 | valid bool 47 | result string 48 | }{ 49 | { 50 | []string{"invalid"}, 51 | false, 52 | translationID, 53 | }, 54 | { 55 | []string{"invalid", "invalid2"}, 56 | false, 57 | translationID, 58 | }, 59 | { 60 | []string{"invalid", "en-US"}, 61 | true, 62 | englishTranslation, 63 | }, 64 | { 65 | []string{"en-US", "invalid"}, 66 | true, 67 | englishTranslation, 68 | }, 69 | { 70 | []string{"en-US", "fr-FR"}, 71 | true, 72 | englishTranslation, 73 | }, 74 | } 75 | 76 | for _, test := range tests { 77 | tf, err := b.Tfunc(test.localeIDs[0], test.localeIDs[1:]...) 78 | if err != nil && test.valid { 79 | t.Errorf("Tfunc for %v returned error %s", test.localeIDs, err) 80 | } 81 | if err == nil && !test.valid { 82 | t.Errorf("Tfunc for %v returned nil error", test.localeIDs) 83 | } 84 | if result := tf(translationID); result != test.result { 85 | t.Errorf("translation was %s; expected %s", result, test.result) 86 | } 87 | } 88 | } 89 | 90 | func testNewTranslation(t *testing.T, data map[string]interface{}) translation.Translation { 91 | translation, err := translation.NewTranslation(data) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | return translation 96 | } 97 | 98 | /* 99 | 100 | func bundleFixture(t *testing.T) *Bundle { 101 | l, err := NewLocaleFromString("ar-EG") 102 | if err != nil { 103 | t.Errorf(err.Error()) 104 | } 105 | return &Bundle{ 106 | Locale: l, 107 | localizedStrings: map[string]*LocalizedString{ 108 | "a": &LocalizedString{ 109 | ID: "a", 110 | }, 111 | "b": &LocalizedString{ 112 | ID: "b", 113 | Translation: "translation(b)", 114 | }, 115 | "c": &LocalizedString{ 116 | ID: "c", 117 | Translations: map[PluralCategory]*PluralTranslation{ 118 | Zero: NewPluralTranslation("zero(c)"), 119 | One: NewPluralTranslation("one(c)"), 120 | Two: NewPluralTranslation("two(c)"), 121 | Few: NewPluralTranslation("few(c)"), 122 | Many: NewPluralTranslation("many(c)"), 123 | Other: NewPluralTranslation("other(c)"), 124 | }, 125 | }, 126 | "d": &LocalizedString{ 127 | ID: "d", 128 | Translations: map[PluralCategory]*PluralTranslation{ 129 | Zero: NewPluralTranslation("zero(d)"), 130 | One: NewPluralTranslation("one(d)"), 131 | }, 132 | }, 133 | }, 134 | } 135 | } 136 | */ 137 | -------------------------------------------------------------------------------- /static/js/service.js: -------------------------------------------------------------------------------- 1 | function ServiceResult() { 2 | var processJSONDataRaw = function (data) { 3 | try { 4 | this.Data = data; 5 | this.SuccessCallback.call(this); 6 | } 7 | 8 | catch (e) { 9 | // TODO: Add Err Handler. Localize Message. 10 | alert("Error Processing Request : " + e); 11 | } 12 | }; 13 | 14 | var processJSONData = function (data) { 15 | try { 16 | this.Result = data.Result; 17 | this.ResultString = data.ResultString; 18 | this.ResultObject = data.ResultObject; 19 | 20 | switch (this.Result) 21 | { 22 | case 0: // Success 23 | this.SuccessCallback.call(this); 24 | break; 25 | 26 | case 100: // Validation Error 27 | if (this.ValidationCallback !== undefined) 28 | { 29 | this.ValidationCallback.call(this); 30 | } 31 | 32 | break; 33 | 34 | case 200: // Session Timeout Error 35 | alert(ResultString); 36 | if (locationUrls.Logout === undefined) 37 | { 38 | window.location.href = '/'; 39 | return; 40 | } 41 | 42 | window.location.href = locationUrls.Logout; 43 | break; 44 | 45 | default: // Other Error 46 | alert('Error: ' + this.ResultString); 47 | if (this.ErrorCallback !== undefined) 48 | { 49 | this.ErrorCallback.call(this); 50 | } 51 | break; 52 | } 53 | } 54 | 55 | catch (e) { 56 | // TODO: Add Err Handler. Localize Message. 57 | alert("Error Processing Request : " + e); 58 | } 59 | }; 60 | 61 | // Handles Ajax Error 62 | var processError = function (objXHR, textStatus, error) { 63 | try { 64 | alert('Error: ' + textStatus); 65 | this.ErrorCallback.call(this); 66 | } 67 | 68 | catch (e) { 69 | } 70 | }; 71 | 72 | // Object Definition 73 | return { 74 | Data: '', 75 | Result: '', 76 | ResultString: '', 77 | ResultObject: '', 78 | SuccessCallback: function () {}, 79 | ValidationCallback: '', 80 | ErrorCallback: '', 81 | Baggage: '', 82 | 83 | getJSONDataBasic: function (url, data, callBack, baggage) { 84 | //Calls Base Method 85 | this.getJSONData(url, data, callback, callback, callback, baggage); 86 | }, 87 | 88 | // Method to Post Data via JSON 89 | getJSONData: function (url, data, callback, validationCallBack, errorCallBack, baggage) { 90 | this.SuccessCallback = callback; 91 | this.ValidationCallback = validationCallBack; 92 | this.ErrorCallback = errorCallBack; 93 | this.Baggage = baggage; 94 | 95 | $.ajax({ 96 | url: url, 97 | data: data, 98 | dataType: 'json', 99 | error: processError, 100 | success: processJSONData, 101 | context: this, 102 | type: 'POST' 103 | }); 104 | }, 105 | 106 | // Method to Post Data via JSON 107 | getJSONDataRaw: function (url, data, callback) { 108 | this.SuccessCallback = callback; 109 | $.ajax({ 110 | url: url, 111 | data: data, 112 | dataType: 'json', 113 | error: processJSONDataRaw, 114 | success: processJSONDataRaw, 115 | context: this, 116 | type: 'POST' 117 | }); 118 | } 119 | }; 120 | } -------------------------------------------------------------------------------- /go-i18n/i18n/translation/template_test.go: -------------------------------------------------------------------------------- 1 | package translation 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | //"launchpad.net/goyaml" 7 | "testing" 8 | gotemplate "text/template" 9 | ) 10 | 11 | func TestNilTemplate(t *testing.T) { 12 | expected := "hello" 13 | tmpl := &template{ 14 | tmpl: nil, 15 | src: expected, 16 | } 17 | if actual := tmpl.Execute(nil); actual != expected { 18 | t.Errorf("Execute(nil) returned %s; expected %s", actual, expected) 19 | } 20 | } 21 | 22 | func TestMarshalText(t *testing.T) { 23 | tmpl := &template{ 24 | tmpl: gotemplate.Must(gotemplate.New("id").Parse("this is a {{.foo}} template")), 25 | src: "boom", 26 | } 27 | expectedBuf := []byte(tmpl.src) 28 | if buf, err := tmpl.MarshalText(); !bytes.Equal(buf, expectedBuf) || err != nil { 29 | t.Errorf("MarshalText() returned %#v, %#v; expected %#v, nil", buf, err, expectedBuf) 30 | } 31 | } 32 | 33 | func TestUnmarshalText(t *testing.T) { 34 | tmpl := &template{} 35 | tmpl.UnmarshalText([]byte("hello {{.World}}")) 36 | result := tmpl.Execute(map[string]string{ 37 | "World": "world!", 38 | }) 39 | expected := "hello world!" 40 | if result != expected { 41 | t.Errorf("expected %#v; got %#v", expected, result) 42 | } 43 | } 44 | 45 | /* 46 | func TestYAMLMarshal(t *testing.T) { 47 | src := "hello {{.World}}" 48 | tmpl, err := newTemplate(src) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | buf, err := goyaml.Marshal(tmpl) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | if !bytes.Equal(buf, []byte(src)) { 57 | t.Fatalf(`expected "%s"; got "%s"`, src, buf) 58 | } 59 | } 60 | 61 | func TestYAMLUnmarshal(t *testing.T) { 62 | buf := []byte(`Tmpl: "hello"`) 63 | 64 | var out struct { 65 | Tmpl *template 66 | } 67 | var foo map[string]string 68 | if err := goyaml.Unmarshal(buf, &foo); err != nil { 69 | t.Fatal(err) 70 | } 71 | if out.Tmpl == nil { 72 | t.Fatalf("out.Tmpl was nil") 73 | } 74 | if out.Tmpl.tmpl == nil { 75 | t.Fatalf("out.Tmpl.tmpl was nil") 76 | } 77 | if expected := "hello {{.World}}"; out.Tmpl.src != expected { 78 | t.Fatalf("expected %s; got %s", expected, out.Tmpl.src) 79 | } 80 | } 81 | 82 | func TestGetYAML(t *testing.T) { 83 | src := "hello" 84 | tmpl := &template{ 85 | tmpl: nil, 86 | src: src, 87 | } 88 | if tag, value := tmpl.GetYAML(); tag != "" || value != src { 89 | t.Errorf("GetYAML() returned (%#v, %#v); expected (%#v, %#v)", tag, value, "", src) 90 | } 91 | } 92 | 93 | func TestSetYAML(t *testing.T) { 94 | tmpl := &template{} 95 | tmpl.SetYAML("tagDoesntMatter", "hello {{.World}}") 96 | result := tmpl.Execute(map[string]string{ 97 | "World": "world!", 98 | }) 99 | expected := "hello world!" 100 | if result != expected { 101 | t.Errorf("expected %#v; got %#v", expected, result) 102 | } 103 | } 104 | */ 105 | 106 | func BenchmarkExecuteNilTemplate(b *testing.B) { 107 | template := &template{src: "hello world"} 108 | b.ResetTimer() 109 | for i := 0; i < b.N; i++ { 110 | template.Execute(nil) 111 | } 112 | } 113 | 114 | func BenchmarkExecuteHelloWorldTemplate(b *testing.B) { 115 | template, err := newTemplate("hello world") 116 | if err != nil { 117 | b.Fatal(err) 118 | } 119 | b.ResetTimer() 120 | for i := 0; i < b.N; i++ { 121 | template.Execute(nil) 122 | } 123 | } 124 | 125 | // Executing a simple template like this is ~6x slower than Sprintf 126 | // but it is still only a few microseconds which should be sufficiently fast. 127 | // The benefit is that we have nice semantic tags in the translation. 128 | func BenchmarkExecuteHelloNameTemplate(b *testing.B) { 129 | template, err := newTemplate("hello {{.Name}}") 130 | if err != nil { 131 | b.Fatal(err) 132 | } 133 | b.ResetTimer() 134 | for i := 0; i < b.N; i++ { 135 | template.Execute(map[string]string{ 136 | "Name": "Nick", 137 | }) 138 | } 139 | } 140 | 141 | func BenchmarkSprintf(b *testing.B) { 142 | b.ResetTimer() 143 | for i := 0; i < b.N; i++ { 144 | fmt.Sprintf("hello %s", "nick") 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /test/endpointTests/buoyEndpoints_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package endpointTests implements tests for the buoy endpoints. 6 | package endpointTests 7 | 8 | import ( 9 | "net/http" 10 | "net/http/httptest" 11 | "testing" 12 | 13 | "encoding/json" 14 | 15 | "github.com/astaxie/beego" 16 | log "github.com/goinggo/tracelog" 17 | . "github.com/smartystreets/goconvey/convey" 18 | ) 19 | 20 | // TestStation is a sample to run an endpoint test 21 | func TestStation(t *testing.T) { 22 | r, _ := http.NewRequest("GET", "/buoy/station/42002", nil) 23 | w := httptest.NewRecorder() 24 | beego.BeeApp.Handlers.ServeHTTP(w, r) 25 | 26 | log.Trace("testing", "TestStation", "Code[%d]\n%s", w.Code, w.Body.String()) 27 | 28 | var response struct { 29 | StationID string `json:"station_id"` 30 | Name string `json:"name"` 31 | LocDesc string `json:"location_desc"` 32 | Condition struct { 33 | Type string `json:"type"` 34 | Coordinates []float64 `json:"coordinates"` 35 | } `json:"condition"` 36 | Location struct { 37 | WindSpeed float64 `json:"wind_speed_milehour"` 38 | WindDirection int `json:"wind_direction_degnorth"` 39 | WindGust float64 `json:"gust_wind_speed_milehour"` 40 | } `json:"location"` 41 | } 42 | json.Unmarshal(w.Body.Bytes(), &response) 43 | 44 | Convey("Subject: Test Station Endpoint\n", t, func() { 45 | Convey("Status Code Should Be 200", func() { 46 | So(w.Code, ShouldEqual, 200) 47 | }) 48 | Convey("The Result Should Not Be Empty", func() { 49 | So(w.Body.Len(), ShouldBeGreaterThan, 0) 50 | }) 51 | Convey("There Should Be A Result For Station 42002", func() { 52 | So(response.StationID, ShouldEqual, "42002") 53 | }) 54 | }) 55 | } 56 | 57 | // TestInvalidStation is a sample to run an endpoint test that returns 58 | // an empty result set 59 | func TestInvalidStation(t *testing.T) { 60 | r, _ := http.NewRequest("GET", "/buoy/station/000000", nil) 61 | w := httptest.NewRecorder() 62 | beego.BeeApp.Handlers.ServeHTTP(w, r) 63 | 64 | log.Trace("testing", "TestStation", "Code[%d]\n%s", w.Code, w.Body.String()) 65 | 66 | var response struct { 67 | StationID string `json:"station_id"` 68 | Name string `json:"name"` 69 | LocDesc string `json:"location_desc"` 70 | Condition struct { 71 | Type string `json:"type"` 72 | Coordinates []float64 `json:"coordinates"` 73 | } `json:"condition"` 74 | Location struct { 75 | WindSpeed float64 `json:"wind_speed_milehour"` 76 | WindDirection int `json:"wind_direction_degnorth"` 77 | WindGust float64 `json:"gust_wind_speed_milehour"` 78 | } `json:"location"` 79 | } 80 | json.Unmarshal(w.Body.Bytes(), &response) 81 | 82 | Convey("Subject: Test Station Endpoint\n", t, func() { 83 | Convey("Status Code Should Be 200", func() { 84 | So(w.Code, ShouldEqual, 200) 85 | }) 86 | Convey("The Result Should Not Be Empty", func() { 87 | So(w.Body.Len(), ShouldBeGreaterThan, 0) 88 | }) 89 | Convey("The Result Should Be Empty For Station 00000", func() { 90 | So(response.StationID, ShouldBeBlank) 91 | }) 92 | }) 93 | } 94 | 95 | // TestInvalidStation is a sample to run an endpoint test that returns 96 | // an empty result set 97 | func TestMissingStation(t *testing.T) { 98 | r, _ := http.NewRequest("GET", "/buoy/station/420", nil) 99 | w := httptest.NewRecorder() 100 | beego.BeeApp.Handlers.ServeHTTP(w, r) 101 | 102 | log.Trace("testing", "TestStation", "Code[%d]\n%s", w.Code, w.Body.String()) 103 | 104 | var err struct { 105 | Errors []string `json:"errors"` 106 | } 107 | json.Unmarshal(w.Body.Bytes(), &err) 108 | 109 | Convey("Subject: Test Station Endpoint\n", t, func() { 110 | Convey("Status Code Should Be 409", func() { 111 | So(w.Code, ShouldEqual, 409) 112 | }) 113 | Convey("The Result Should Not Be Empty", func() { 114 | So(w.Body.Len(), ShouldBeGreaterThan, 0) 115 | }) 116 | Convey("The Should Be An Error In The Result", func() { 117 | So(len(err.Errors), ShouldEqual, 1) 118 | }) 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /go-i18n/goi18n/merge.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | //"launchpad.net/goyaml" 8 | "github.com/goinggo/beego-mgo/go-i18n/i18n/bundle" 9 | "github.com/goinggo/beego-mgo/go-i18n/i18n/locale" 10 | "github.com/goinggo/beego-mgo/go-i18n/i18n/translation" 11 | "path/filepath" 12 | "reflect" 13 | "sort" 14 | ) 15 | 16 | type mergeCommand struct { 17 | translationFiles []string 18 | sourceLocaleID string 19 | outdir string 20 | format string 21 | } 22 | 23 | func (mc *mergeCommand) execute() error { 24 | if len(mc.translationFiles) < 1 { 25 | return fmt.Errorf("need at least one translation file to parse") 26 | } 27 | 28 | if _, err := locale.New(mc.sourceLocaleID); err != nil { 29 | return fmt.Errorf("invalid source locale %s: %s", mc.sourceLocaleID, err) 30 | } 31 | 32 | marshal, err := newMarshalFunc(mc.format) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | bundle := bundle.New() 38 | for _, tf := range mc.translationFiles { 39 | if err := bundle.LoadTranslationFile(tf); err != nil { 40 | return fmt.Errorf("failed to load translation file %s because %s\n", tf, err) 41 | } 42 | } 43 | 44 | translations := bundle.Translations() 45 | sourceTranslations := translations[mc.sourceLocaleID] 46 | for translationID, src := range sourceTranslations { 47 | for _, localeTranslations := range translations { 48 | if dst := localeTranslations[translationID]; dst == nil || reflect.TypeOf(src) != reflect.TypeOf(dst) { 49 | localeTranslations[translationID] = src.UntranslatedCopy() 50 | } 51 | } 52 | } 53 | 54 | for localeID, localeTranslations := range translations { 55 | locale := locale.MustNew(localeID) 56 | all := filter(localeTranslations, func(t translation.Translation) translation.Translation { 57 | return t.Normalize(locale.Language) 58 | }) 59 | if err := mc.writeFile("all", all, localeID, marshal); err != nil { 60 | return err 61 | } 62 | 63 | untranslated := filter(localeTranslations, func(t translation.Translation) translation.Translation { 64 | if t.Incomplete(locale.Language) { 65 | return t.Normalize(locale.Language).Backfill(sourceTranslations[t.ID()]) 66 | } 67 | return nil 68 | }) 69 | if err := mc.writeFile("untranslated", untranslated, localeID, marshal); err != nil { 70 | return err 71 | } 72 | } 73 | return nil 74 | } 75 | 76 | type marshalFunc func(interface{}) ([]byte, error) 77 | 78 | func (mc *mergeCommand) writeFile(label string, translations []translation.Translation, localeID string, marshal marshalFunc) error { 79 | sort.Sort(translation.SortableByID(translations)) 80 | buf, err := marshal(marshalInterface(translations)) 81 | if err != nil { 82 | return fmt.Errorf("failed to marshal %s strings to %s because %s", localeID, mc.format, err) 83 | } 84 | filename := filepath.Join(mc.outdir, fmt.Sprintf("%s.%s.%s", localeID, label, mc.format)) 85 | if err := ioutil.WriteFile(filename, buf, 0666); err != nil { 86 | return fmt.Errorf("failed to write %s because %s", filename, err) 87 | } 88 | return nil 89 | } 90 | 91 | func filter(translations map[string]translation.Translation, filter func(translation.Translation) translation.Translation) []translation.Translation { 92 | filtered := make([]translation.Translation, 0, len(translations)) 93 | for _, translation := range translations { 94 | if t := filter(translation); t != nil { 95 | filtered = append(filtered, t) 96 | } 97 | } 98 | return filtered 99 | 100 | } 101 | 102 | func newMarshalFunc(format string) (marshalFunc, error) { 103 | switch format { 104 | case "json": 105 | return func(v interface{}) ([]byte, error) { 106 | return json.MarshalIndent(v, "", " ") 107 | }, nil 108 | /* 109 | case "yaml": 110 | return func(v interface{}) ([]byte, error) { 111 | return goyaml.Marshal(v) 112 | }, nil 113 | */ 114 | } 115 | return nil, fmt.Errorf("unsupported format: %s\n", format) 116 | } 117 | 118 | func marshalInterface(translations []translation.Translation) []interface{} { 119 | mi := make([]interface{}, len(translations)) 120 | for i, translation := range translations { 121 | mi[i] = translation.MarshalInterface() 122 | } 123 | return mi 124 | } 125 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* CSS Document */ 3 | 4 | body { 5 | background: rgba(232,234,242,1); 6 | font-family: 'Rambla', sans-serif; 7 | } 8 | a { 9 | color: #00aeff; 10 | } 11 | a, a:hover, a:active { 12 | outline: 0 none !important; 13 | } 14 | h2 { 15 | margin-top: 1px; 16 | } 17 | input[type="button"] { 18 | font-size: 16px; 19 | } 20 | .bootstrap-select:not([class*="span"]):not([class*="col-"]):not([class*="form-control"]) { 21 | width: 240px; 22 | } 23 | .navbar { 24 | border-radius: 0px; 25 | border: none; 26 | } 27 | .navbar-inverse { 28 | background: rgba(52,50,61,0.95); 29 | border-color: #080808; 30 | } 31 | .navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { 32 | background-color: rgba(0,0,0,0.5); 33 | color: #FFFFFF; 34 | } 35 | .navbar-inverse .navbar-brand { 36 | color: #FFFFFF; 37 | } 38 | hr { 39 | border-color: rgba(52, 50, 61, 0.3); 40 | box-shadow: 0px 1px 0px rgba(255,255,255,1); 41 | margin-top: 1px; 42 | margin-bottom: 10px; 43 | } 44 | .alert { 45 | font-size: 18px; 46 | } 47 | /*-- Scrolling Background --*/ 48 | #top { 49 | background: url(../img/posters.jpg); 50 | box-shadow: 0px 0px 180px rgba(0,0,0,1) inset; 51 | } 52 | 53 | /*-- Overide for Responsive Styles --*/ 54 | .row { 55 | margin-left: 0px !important; 56 | margin-right: 0px !important; 57 | } 58 | 59 | /*-- Code Section Styles --*/ 60 | #code-section { 61 | background: rgba(255,255,255,0.5); 62 | border-bottom: 1px solid rgba(68,67,80,0.2); 63 | box-shadow: 0px 1px 0px rgba(255,255,255,1.0); 64 | padding: 0 0 30px; 65 | } 66 | #code-section textarea { 67 | width: 90%; 68 | min-height: 600px; 69 | font-size: 20px; 70 | float: left; 71 | white-space: pre; 72 | word-wrap: normal; 73 | overflow-x: scroll; 74 | } 75 | #code-section { 76 | width: 90%; 77 | font-size: 20px; 78 | float: left; 79 | } 80 | 81 | #code-section { 82 | margin: 10px; 83 | } 84 | 85 | /*-- Input Section Styles --*/ 86 | #input-section { 87 | } 88 | #input-section textarea { 89 | min-height: 500px; 90 | } 91 | textarea.pasted-textarea { 92 | background: none repeat scroll 0 0 #FAFBFF; 93 | font-size: 20px; 94 | } 95 | textarea.result-textarea { 96 | color: #090; 97 | font-size: 18px; 98 | } 99 | textarea.form-control { 100 | font-size: 18px; 101 | } 102 | /*-- Tab Section Styles --*/ 103 | p.nav-text { 104 | border-left: 1px solid rgba(255, 255, 255, 0.2); 105 | box-shadow: -1px 0 0 rgba(0, 0, 0, 0.5); 106 | color: rgba(255, 255, 255, 0.8); 107 | float: left; 108 | font-size: 15px; 109 | margin: 0; 110 | padding: 15px 0 15px 15px; 111 | } 112 | .tab-pane { 113 | background: #FFFFFF; 114 | border-right: 1px solid #DDDDDD; 115 | border-bottom: 1px solid #DDDDDD; 116 | border-left: 1px solid #DDDDDD; 117 | border-radius: 0px 0px 4px 4px; 118 | padding: 15px 0; 119 | 120 | } 121 | .tab-row { 122 | padding: 0 5px; 123 | } 124 | .tab-content { 125 | padding: 0px 20px 0px 20px; 126 | } 127 | .button-row { 128 | padding: 10px 0px 0px 0px; 129 | } 130 | .nav > li > a:hover, .nav > li > a:focus { 131 | background-color: rgba(255,255,255,0.5); 132 | text-decoration: none; 133 | } 134 | .nav-tabs > li > a { 135 | font-size: 18px; 136 | text-shadow: 0px 1px 0px #FFFFFF; 137 | } 138 | .nav > li > a { 139 | padding: 15px 20px; 140 | } 141 | 142 | /*-- Overide for select --*/ 143 | .bootstrap-select.btn-group .btn .filter-option { 144 | font-size: 16px; 145 | } 146 | 147 | /*-- Modal --*/ 148 | .modal-button { 149 | margin-left: 10px; 150 | font-size: 16px; 151 | padding: 5px 12px; 152 | } 153 | 154 | .modal-content { 155 | width: 800px; 156 | } 157 | 158 | .modal-dialog { 159 | width: 800px; 160 | } 161 | 162 | .modal-body { 163 | background-color: #E8EAF2; 164 | padding: 10px 0px 15px 0px; 165 | } 166 | 167 | .modal-row { 168 | padding: 0px 0px 0px 10px; 169 | } 170 | 171 | .modal-footer { 172 | padding: 19px 20px 20px; 173 | margin-top: 0px; 174 | text-align: right; 175 | border-top: 0px solid #e5e5e5; 176 | } 177 | 178 | /*-- Lists --*/ 179 | .list-group-item { 180 | font-size: 17px; 181 | } 182 | .list-group-item.name { 183 | background: none repeat scroll 0 0 #FAFBFF; 184 | font-size: 20px; 185 | font-weight: bold; 186 | } 187 | .list-group-item b { 188 | margin: 0px 5px 0px 0px; 189 | } 190 | 191 | /*-- Buoy --*/ 192 | #load-station-button, #load-station-button-json { 193 | margin: 0 0 10px !important; 194 | } -------------------------------------------------------------------------------- /localize/messages.go: -------------------------------------------------------------------------------- 1 | // Package localize : messages.go package provides support for handling different languages and cultures 2 | package localize 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path" 10 | 11 | "github.com/goinggo/beego-mgo/go-i18n/i18n" 12 | "github.com/goinggo/beego-mgo/go-i18n/i18n/locale" 13 | "github.com/goinggo/beego-mgo/go-i18n/i18n/translation" 14 | "github.com/goinggo/tracelog" 15 | ) 16 | 17 | var ( 18 | // T is the translate function for the default locale 19 | T i18n.TranslateFunc 20 | ) 21 | 22 | // Init initializes the local environment 23 | func Init(defaultLocale string) error { 24 | tracelog.Startedf("localize", "Init", "defaultLocal[%s]", defaultLocale) 25 | 26 | switch defaultLocale { 27 | case "en-US": 28 | LoadJSON(defaultLocale, EnUS) 29 | default: 30 | return fmt.Errorf("Unsupported Locale: %s", defaultLocale) 31 | } 32 | 33 | // Obtain the default translation function for use 34 | var err error 35 | if T, err = NewTranslation(defaultLocale, defaultLocale); err != nil { 36 | return err 37 | } 38 | 39 | tracelog.Completed("localize", "Init") 40 | return nil 41 | } 42 | 43 | // NewTranslation obtains a translation function object for the 44 | // specified locales 45 | func NewTranslation(userLocale string, defaultLocale string) (i18n.TranslateFunc, error) { 46 | return i18n.Tfunc(userLocale, userLocale) 47 | } 48 | 49 | // LoadJSON takes a json document of translations and manually 50 | // loads them into the system 51 | func LoadJSON(userLocale string, translationDocument string) error { 52 | tracelog.Startedf("localize", "LoadJSON", "userLocale[%s] length[%d]", userLocale, len(translationDocument)) 53 | 54 | var tranDocuments []map[string]interface{} 55 | if err := json.Unmarshal([]byte(translationDocument), &tranDocuments); err != nil { 56 | tracelog.CompletedErrorf(err, "localize", "LoadJSON", "**************>") 57 | return err 58 | } 59 | 60 | for _, tranDocument := range tranDocuments { 61 | tran, err := translation.NewTranslation(tranDocument) 62 | if err != nil { 63 | tracelog.CompletedError(err, "localize", "LoadJSON") 64 | return err 65 | } 66 | 67 | i18n.AddTranslation(locale.MustNew(userLocale), tran) 68 | } 69 | 70 | tracelog.Completed("localize", "LoadJSON") 71 | return nil 72 | } 73 | 74 | // LoadFiles looks for i18n folders inside the current directory and the GOPATH 75 | // to find translation files to load 76 | func LoadFiles(userLocale string, defaultLocal string) error { 77 | gopath := os.Getenv("GOPATH") 78 | pwd, err := os.Getwd() 79 | if err != nil { 80 | tracelog.CompletedError(err, "localize", "LoadFiles") 81 | return err 82 | } 83 | 84 | tracelog.Info("localize", "LoadFiles", "PWD[%s] GOPATH[%s]", pwd, gopath) 85 | 86 | // Load any translation files we can find 87 | searchDirectory(pwd, pwd) 88 | if gopath != "" { 89 | searchDirectory(gopath, pwd) 90 | } 91 | 92 | // Create a translation function for use 93 | T, err = i18n.Tfunc(userLocale, defaultLocal) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | return nil 99 | } 100 | 101 | // searchDirectory recurses through the specified directory looking 102 | // for i18n folders. If found it will load the translations files 103 | func searchDirectory(directory string, pwd string) { 104 | // Read the directory 105 | fileInfos, err := ioutil.ReadDir(directory) 106 | if err != nil { 107 | tracelog.CompletedError(err, "localize", "searchDirectory") 108 | return 109 | } 110 | 111 | // Look for i18n folders 112 | for _, fileInfo := range fileInfos { 113 | if fileInfo.IsDir() == true { 114 | fullPath := fmt.Sprintf("%s/%s", directory, fileInfo.Name()) 115 | 116 | // If this directory is the current directory, ignore it 117 | if fullPath == pwd { 118 | continue 119 | } 120 | 121 | // Is this an i18n folder 122 | if fileInfo.Name() == "i18n" { 123 | loadTranslationFiles(fullPath) 124 | continue 125 | } 126 | 127 | // Look for more sub-directories 128 | searchDirectory(fullPath, pwd) 129 | continue 130 | } 131 | } 132 | } 133 | 134 | // loadTranslationFiles loads the found translation files into the i18n 135 | // messaging system for use by the application 136 | func loadTranslationFiles(directory string) { 137 | // Read the directory 138 | fileInfos, err := ioutil.ReadDir(directory) 139 | if err != nil { 140 | tracelog.CompletedError(err, "localize", "loadTranslationFiles") 141 | return 142 | } 143 | 144 | // Look for JSON files 145 | for _, fileInfo := range fileInfos { 146 | if path.Ext(fileInfo.Name()) != ".json" { 147 | continue 148 | } 149 | 150 | fileName := fmt.Sprintf("%s/%s", directory, fileInfo.Name()) 151 | 152 | tracelog.Info("localize", "loadTranslationFiles", "Loading %s", fileName) 153 | i18n.MustLoadTranslationFile(fileName) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /go-i18n/i18n/bundle/bundle.go: -------------------------------------------------------------------------------- 1 | // Package bundle manages translations for multiple locales. 2 | package bundle 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | // "launchpad.net/goyaml" 9 | "github.com/goinggo/beego-mgo/go-i18n/i18n/locale" 10 | "github.com/goinggo/beego-mgo/go-i18n/i18n/translation" 11 | "path/filepath" 12 | ) 13 | 14 | // TranslateFunc is a copy of i18n.TranslateFunc to avoid a circular dependency. 15 | type TranslateFunc func(translationID string, args ...interface{}) string 16 | 17 | type Bundle struct { 18 | translations map[string]map[string]translation.Translation 19 | } 20 | 21 | func New() *Bundle { 22 | return &Bundle{ 23 | translations: make(map[string]map[string]translation.Translation), 24 | } 25 | } 26 | 27 | func (b *Bundle) MustLoadTranslationFile(filename string) { 28 | if err := b.LoadTranslationFile(filename); err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | func (b *Bundle) LoadTranslationFile(filename string) error { 34 | locale, err := locale.New(filename) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | translations, err := parseTranslationFile(filename) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | b.AddTranslation(locale, translations...) 45 | return nil 46 | } 47 | 48 | func parseTranslationFile(filename string) ([]translation.Translation, error) { 49 | var unmarshalFunc func([]byte, interface{}) error 50 | switch format := filepath.Ext(filename); format { 51 | case ".json": 52 | unmarshalFunc = json.Unmarshal 53 | /* 54 | case ".yaml": 55 | unmarshalFunc = goyaml.Unmarshal 56 | */ 57 | default: 58 | return nil, fmt.Errorf("unsupported file extension %s", format) 59 | } 60 | fileBytes, err := ioutil.ReadFile(filename) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | var translationsData []map[string]interface{} 66 | if len(fileBytes) > 0 { 67 | if err := unmarshalFunc(fileBytes, &translationsData); err != nil { 68 | return nil, err 69 | } 70 | } 71 | 72 | translations := make([]translation.Translation, 0, len(translationsData)) 73 | for i, translationData := range translationsData { 74 | t, err := translation.NewTranslation(translationData) 75 | if err != nil { 76 | return nil, fmt.Errorf("unable to parse translation #%d in %s because %s\n%v", i, filename, err, translationData) 77 | } 78 | translations = append(translations, t) 79 | } 80 | return translations, nil 81 | } 82 | 83 | func (b *Bundle) AddTranslation(locale *locale.Locale, translations ...translation.Translation) { 84 | if b.translations[locale.ID] == nil { 85 | b.translations[locale.ID] = make(map[string]translation.Translation, len(translations)) 86 | } 87 | currentTranslations := b.translations[locale.ID] 88 | for _, newTranslation := range translations { 89 | if currentTranslation := currentTranslations[newTranslation.ID()]; currentTranslation != nil { 90 | currentTranslations[newTranslation.ID()] = currentTranslation.Merge(newTranslation) 91 | } else { 92 | currentTranslations[newTranslation.ID()] = newTranslation 93 | } 94 | } 95 | } 96 | 97 | func (b *Bundle) Translations() map[string]map[string]translation.Translation { 98 | return b.translations 99 | } 100 | 101 | func (b *Bundle) MustTfunc(localeID string, localeIDs ...string) TranslateFunc { 102 | tf, err := b.Tfunc(localeID, localeIDs...) 103 | if err != nil { 104 | panic(err) 105 | } 106 | return tf 107 | } 108 | 109 | func (b *Bundle) Tfunc(localeID string, localeIDs ...string) (tf TranslateFunc, err error) { 110 | var l *locale.Locale 111 | l, err = locale.New(localeID) 112 | if err != nil { 113 | for _, localeID := range localeIDs { 114 | l, err = locale.New(localeID) 115 | if err == nil { 116 | break 117 | } 118 | } 119 | } 120 | return func(translationID string, args ...interface{}) string { 121 | return b.translate(l, translationID, args...) 122 | }, err 123 | } 124 | 125 | func (b *Bundle) translate(locale *locale.Locale, translationID string, args ...interface{}) string { 126 | if locale == nil { 127 | return translationID 128 | } 129 | 130 | translations := b.translations[locale.ID] 131 | if translations == nil { 132 | return translationID 133 | } 134 | 135 | translation := translations[translationID] 136 | if translation == nil { 137 | return translationID 138 | } 139 | 140 | var count interface{} 141 | if len(args) > 0 && isNumber(args[0]) { 142 | count = args[0] 143 | args = args[1:] 144 | } 145 | 146 | pluralCategory, _ := locale.Language.PluralCategory(count) 147 | template := translation.Template(pluralCategory) 148 | if template == nil { 149 | return translationID 150 | } 151 | 152 | var data map[string]interface{} 153 | if len(args) > 0 { 154 | data, _ = args[0].(map[string]interface{}) 155 | } 156 | 157 | if isNumber(count) { 158 | if data == nil { 159 | data = map[string]interface{}{"Count": count} 160 | } else { 161 | data["Count"] = count 162 | } 163 | } 164 | 165 | s := template.Execute(data) 166 | if s == "" { 167 | return translationID 168 | } 169 | return s 170 | } 171 | 172 | func isNumber(n interface{}) bool { 173 | switch n.(type) { 174 | case int, int8, int16, int32, int64, string: 175 | return true 176 | } 177 | return false 178 | } 179 | -------------------------------------------------------------------------------- /go-i18n/i18n/i18n.go: -------------------------------------------------------------------------------- 1 | // Package i18n supports string translations with variable substitution and CLDR pluralization. 2 | // It is intended to be used in conjunction with github.com/finapps/go-i18n/goi18n, 3 | // although that is not strictly required. 4 | // 5 | // Initialization 6 | // 7 | // Your Go program should load translations during its intialization. 8 | // i18n.MustLoadTranslationFile("path/to/fr-FR.all.json") 9 | // If your translations are in a file format not supported by (Must)?LoadTranslationFile, 10 | // then you can use the AddTranslation function to manually add translations. 11 | // 12 | // Fetching a translation 13 | // 14 | // Use Tfunc or MustTfunc to fetch a TranslateFunc that will return the translated string for a specific locale. 15 | // The TranslateFunc will be bound to the first valid locale passed to Tfunc. 16 | // userLocale = "ar-AR" // user preference, accept header, language cookie 17 | // defaultLocale = "en-US" // known valid locale 18 | // T, err := i18n.Tfunc(userLocale, defaultLocale) 19 | // fmt.Println(T("Hello world")) 20 | // 21 | // Usually it is a good idea to identify strings by a generic id rather than the English translation, 22 | // but the rest of this documentation will continue to use the English translation for readability. 23 | // T("program_greeting") 24 | // 25 | // Variables 26 | // 27 | // TranslateFunc supports strings that have variables using the text/template syntax. 28 | // T("Hello {{.Person}}", map[string]interface{}{ 29 | // "Person": "Bob", 30 | // }) 31 | // 32 | // Pluralization 33 | // 34 | // TranslateFunc supports the pluralization of strings using the CLDR pluralization rules defined here: 35 | // http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html 36 | // T("You have {{.Count}} unread emails.", 2) 37 | // T("I am {{.Count}} meters tall.", "1.7") 38 | // 39 | // Plural strings may also have variables. 40 | // T("{{.Person}} has {{.Count}} unread emails", 2, map[string]interface{}{ 41 | // "Person": "Bob", 42 | // }) 43 | // 44 | // Compound plural strings can be created with nesting. 45 | // T("{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 3, map[string]interface{}{ 46 | // "Person": "Bob", 47 | // "Timeframe": T("{{.Count}} days", 2), 48 | // }) 49 | // 50 | // Templates 51 | // 52 | // You can use the .Funcs() method of a text/template or html/template to register a TranslateFunc 53 | // for usage inside of that template. 54 | package i18n 55 | 56 | import ( 57 | "github.com/goinggo/beego-mgo/go-i18n/i18n/bundle" 58 | "github.com/goinggo/beego-mgo/go-i18n/i18n/locale" 59 | "github.com/goinggo/beego-mgo/go-i18n/i18n/translation" 60 | ) 61 | 62 | // TranslateFunc returns the translation of the string identified by translationID. 63 | // 64 | // If translationID is a non-plural form, then the first variadic argument may be a map[string]interface{} 65 | // that contains template data. 66 | // 67 | // If translationID is a plural form, then the first variadic argument must be an integer type 68 | // (int, int8, int16, int32, int64) or a float formatted as a string (e.g. "123.45"). 69 | // The second variadic argument may be a map[string]interface{} that contains template data. 70 | type TranslateFunc func(translationID string, args ...interface{}) string 71 | 72 | // IdentityTfunc returns a TranslateFunc that always returns the translationID passed to it. 73 | // 74 | // It is a useful placeholder when parsing a text/template or html/template 75 | // before the actual Tfunc is available. 76 | func IdentityTfunc() TranslateFunc { 77 | return func(translationID string, args ...interface{}) string { 78 | return translationID 79 | } 80 | } 81 | 82 | var defaultBundle = bundle.New() 83 | 84 | // MustLoadTranslationFile is similar to LoadTranslationFile 85 | // except it panics if an error happens. 86 | func MustLoadTranslationFile(filename string) { 87 | defaultBundle.MustLoadTranslationFile(filename) 88 | } 89 | 90 | // LoadTranslationFile loads the translations from filename into memory. 91 | // 92 | // The locale that the translations are associated with is parsed from the filename. 93 | // 94 | // Generally you should load translation files once during your program's initialization. 95 | func LoadTranslationFile(filename string) error { 96 | return defaultBundle.LoadTranslationFile(filename) 97 | } 98 | 99 | // AddTranslation adds translations for a locale. 100 | // 101 | // It is useful if your translations are in a format not supported by LoadTranslationFile. 102 | func AddTranslation(locale *locale.Locale, translations ...translation.Translation) { 103 | defaultBundle.AddTranslation(locale, translations...) 104 | } 105 | 106 | // MustTfunc is similar to Tfunc except it panics if an error happens. 107 | func MustTfunc(localeID string, localeIDs ...string) TranslateFunc { 108 | return TranslateFunc(defaultBundle.MustTfunc(localeID, localeIDs...)) 109 | } 110 | 111 | // Tfunc returns a TranslateFunc that will be bound to the first valid locale from its parameters. 112 | func Tfunc(localeID string, localeIDs ...string) (TranslateFunc, error) { 113 | tf, err := defaultBundle.Tfunc(localeID, localeIDs...) 114 | return TranslateFunc(tf), err 115 | } 116 | -------------------------------------------------------------------------------- /controllers/baseController/baseController.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of baseController source code is governed by a BSD-style 3 | // license that can be found in the LICENSE handle. 4 | 5 | // Package baseController implements boilerplate code for all baseControllers. 6 | package baseController 7 | 8 | import ( 9 | "reflect" 10 | "runtime" 11 | 12 | "fmt" 13 | 14 | "github.com/astaxie/beego" 15 | "github.com/astaxie/beego/validation" 16 | "github.com/goinggo/beego-mgo/localize" 17 | "github.com/goinggo/beego-mgo/services" 18 | "github.com/goinggo/beego-mgo/utilities/mongo" 19 | log "github.com/goinggo/tracelog" 20 | ) 21 | 22 | //** TYPES 23 | 24 | type ( 25 | // BaseController composes all required types and behavior. 26 | BaseController struct { 27 | beego.Controller 28 | services.Service 29 | } 30 | ) 31 | 32 | //** INTERCEPT FUNCTIONS 33 | 34 | // Prepare is called prior to the baseController method. 35 | func (baseController *BaseController) Prepare() { 36 | baseController.UserID = baseController.GetString("userID") 37 | if baseController.UserID == "" { 38 | baseController.UserID = baseController.GetString(":userID") 39 | } 40 | if baseController.UserID == "" { 41 | baseController.UserID = "Unknown" 42 | } 43 | 44 | if err := baseController.Service.Prepare(); err != nil { 45 | log.Errorf(err, baseController.UserID, "BaseController.Prepare", baseController.Ctx.Request.URL.Path) 46 | baseController.ServeError(err) 47 | return 48 | } 49 | 50 | log.Trace(baseController.UserID, "BaseController.Prepare", "UserID[%s] Path[%s]", baseController.UserID, baseController.Ctx.Request.URL.Path) 51 | } 52 | 53 | // Finish is called once the baseController method completes. 54 | func (baseController *BaseController) Finish() { 55 | defer func() { 56 | if baseController.MongoSession != nil { 57 | mongo.CloseSession(baseController.UserID, baseController.MongoSession) 58 | baseController.MongoSession = nil 59 | } 60 | }() 61 | 62 | log.Completedf(baseController.UserID, "Finish", baseController.Ctx.Request.URL.Path) 63 | } 64 | 65 | //** VALIDATION 66 | 67 | // ParseAndValidate will run the params through the validation framework and then 68 | // response with the specified localized or provided message. 69 | func (baseController *BaseController) ParseAndValidate(params interface{}) bool { 70 | // This is not working anymore :( 71 | if err := baseController.ParseForm(params); err != nil { 72 | baseController.ServeError(err) 73 | return false 74 | } 75 | 76 | var valid validation.Validation 77 | ok, err := valid.Valid(params) 78 | if err != nil { 79 | baseController.ServeError(err) 80 | return false 81 | } 82 | 83 | if ok == false { 84 | // Build a map of the Error messages for each field 85 | messages2 := make(map[string]string) 86 | 87 | val := reflect.ValueOf(params).Elem() 88 | for i := 0; i < val.NumField(); i++ { 89 | // Look for an Error tag in the field 90 | typeField := val.Type().Field(i) 91 | tag := typeField.Tag 92 | tagValue := tag.Get("Error") 93 | 94 | // Was there an Error tag 95 | if tagValue != "" { 96 | messages2[typeField.Name] = tagValue 97 | } 98 | } 99 | 100 | // Build the Error response 101 | var errors []string 102 | for _, err := range valid.Errors { 103 | // Match an Error from the validation framework Errors 104 | // to a field name we have a mapping for 105 | message, ok := messages2[err.Field] 106 | if ok == true { 107 | // Use a localized message if one exists 108 | errors = append(errors, localize.T(message)) 109 | continue 110 | } 111 | 112 | // No match, so use the message as is 113 | errors = append(errors, err.Message) 114 | } 115 | 116 | baseController.ServeValidationErrors(errors) 117 | return false 118 | } 119 | 120 | return true 121 | } 122 | 123 | //** EXCEPTIONS 124 | 125 | // ServeError prepares and serves an Error exception. 126 | func (baseController *BaseController) ServeError(err error) { 127 | baseController.Data["json"] = struct { 128 | Error string `json:"Error"` 129 | }{err.Error()} 130 | baseController.Ctx.Output.SetStatus(500) 131 | baseController.ServeJson() 132 | } 133 | 134 | // ServeValidationErrors prepares and serves a validation exception. 135 | func (baseController *BaseController) ServeValidationErrors(Errors []string) { 136 | baseController.Data["json"] = struct { 137 | Errors []string `json:"Errors"` 138 | }{Errors} 139 | baseController.Ctx.Output.SetStatus(409) 140 | baseController.ServeJson() 141 | } 142 | 143 | //** CATCHING PANICS 144 | 145 | // CatchPanic is used to catch any Panic and log exceptions. Returns a 500 as the response. 146 | func (baseController *BaseController) CatchPanic(functionName string) { 147 | if r := recover(); r != nil { 148 | buf := make([]byte, 10000) 149 | runtime.Stack(buf, false) 150 | 151 | log.Warning(baseController.Service.UserID, functionName, "PANIC Defered [%v] : Stack Trace : %v", r, string(buf)) 152 | 153 | baseController.ServeError(fmt.Errorf("%v", r)) 154 | } 155 | } 156 | 157 | //** AJAX SUPPORT 158 | 159 | // AjaxResponse returns a standard ajax response. 160 | func (baseController *BaseController) AjaxResponse(resultCode int, resultString string, data interface{}) { 161 | response := struct { 162 | Result int 163 | ResultString string 164 | ResultObject interface{} 165 | }{ 166 | Result: resultCode, 167 | ResultString: resultString, 168 | ResultObject: data, 169 | } 170 | 171 | baseController.Data["json"] = response 172 | baseController.ServeJson() 173 | } 174 | -------------------------------------------------------------------------------- /go-i18n/i18n/language/language.go: -------------------------------------------------------------------------------- 1 | // Package language defines languages that implement CLDR pluralization. 2 | package language 3 | 4 | import ( 5 | "github.com/goinggo/beego-mgo/go-i18n/i18n/plural" 6 | ) 7 | 8 | // Language is a written human language. 9 | // 10 | // Languages are identified by tags defined by RFC 5646. 11 | // 12 | // Typically language tags are a 2 character language code (ISO 639-1) 13 | // optionally followed by a dash and a 2 character country code (ISO 3166-1). 14 | // (e.g. en, pt-BR) 15 | // 16 | // A Language implements CLDR plural rules as defined here: 17 | // http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html 18 | // http://unicode.org/reports/tr35/tr35-numbers.html#Operands 19 | type Language struct { 20 | ID string 21 | Name string 22 | PluralCategories map[plural.Category]struct{} 23 | PluralFunc func(*plural.Operands) plural.Category 24 | } 25 | 26 | // Alphabetical by English name. 27 | var languages = map[string]*Language{ 28 | // Arabic 29 | "ar": &Language{ 30 | ID: "ar", 31 | PluralCategories: newSet(plural.Zero, plural.One, plural.Two, plural.Few, plural.Many, plural.Other), 32 | PluralFunc: func(ops *plural.Operands) plural.Category { 33 | if ops.W == 0 { 34 | // Integer case 35 | switch ops.I { 36 | case 0: 37 | return plural.Zero 38 | case 1: 39 | return plural.One 40 | case 2: 41 | return plural.Two 42 | default: 43 | mod100 := ops.I % 100 44 | if mod100 >= 3 && mod100 <= 10 { 45 | return plural.Few 46 | } 47 | if mod100 >= 11 { 48 | return plural.Many 49 | } 50 | } 51 | } 52 | return plural.Other 53 | }, 54 | }, 55 | 56 | // Catalan 57 | "ca": &Language{ 58 | ID: "ca", 59 | PluralCategories: newSet(plural.One, plural.Other), 60 | PluralFunc: func(ops *plural.Operands) plural.Category { 61 | if ops.I == 1 && ops.V == 0 { 62 | return plural.One 63 | } 64 | return plural.Other 65 | }, 66 | }, 67 | 68 | // Chinese 69 | // There is no need to distinguish between simplified and traditional 70 | // since they have the same pluralization. 71 | "zh": &Language{ 72 | ID: "zh", 73 | PluralCategories: newSet(plural.Other), 74 | PluralFunc: func(ops *plural.Operands) plural.Category { 75 | return plural.Other 76 | }, 77 | }, 78 | 79 | // Czech 80 | "cs": &Language{ 81 | ID: "cs", 82 | PluralCategories: newSet(plural.One, plural.Few, plural.Many, plural.Other), 83 | PluralFunc: func(ops *plural.Operands) plural.Category { 84 | if ops.I == 1 && ops.V == 0 { 85 | return plural.One 86 | } 87 | if ops.I >= 2 && ops.I <= 4 && ops.V == 0 { 88 | return plural.Few 89 | } 90 | if ops.V > 0 { 91 | return plural.Many 92 | } 93 | return plural.Other 94 | }, 95 | }, 96 | 97 | // Danish 98 | "da": &Language{ 99 | ID: "da", 100 | PluralCategories: newSet(plural.One, plural.Other), 101 | PluralFunc: func(ops *plural.Operands) plural.Category { 102 | if ops.I == 1 || (ops.I == 0 && ops.T != 0) { 103 | return plural.One 104 | } 105 | return plural.Other 106 | }, 107 | }, 108 | 109 | // Dutch 110 | "nl": &Language{ 111 | ID: "nl", 112 | PluralCategories: newSet(plural.One, plural.Other), 113 | PluralFunc: func(ops *plural.Operands) plural.Category { 114 | if ops.I == 1 && ops.V == 0 { 115 | return plural.One 116 | } 117 | return plural.Other 118 | }, 119 | }, 120 | 121 | // English 122 | "en": &Language{ 123 | ID: "en", 124 | PluralCategories: newSet(plural.One, plural.Other), 125 | PluralFunc: func(ops *plural.Operands) plural.Category { 126 | if ops.I == 1 && ops.V == 0 { 127 | return plural.One 128 | } 129 | return plural.Other 130 | }, 131 | }, 132 | 133 | // French 134 | "fr": &Language{ 135 | ID: "fr", 136 | PluralCategories: newSet(plural.One, plural.Other), 137 | PluralFunc: func(ops *plural.Operands) plural.Category { 138 | if ops.I == 0 || ops.I == 1 { 139 | return plural.One 140 | } 141 | return plural.Other 142 | }, 143 | }, 144 | 145 | // German 146 | "de": &Language{ 147 | ID: "de", 148 | PluralCategories: newSet(plural.One, plural.Other), 149 | PluralFunc: func(ops *plural.Operands) plural.Category { 150 | if ops.I == 1 && ops.V == 0 { 151 | return plural.One 152 | } 153 | return plural.Other 154 | }, 155 | }, 156 | 157 | // Italian 158 | "it": &Language{ 159 | ID: "it", 160 | PluralCategories: newSet(plural.One, plural.Other), 161 | PluralFunc: func(ops *plural.Operands) plural.Category { 162 | if ops.I == 1 && ops.V == 0 { 163 | return plural.One 164 | } 165 | return plural.Other 166 | }, 167 | }, 168 | 169 | // Japanese 170 | "ja": &Language{ 171 | ID: "ja", 172 | PluralCategories: newSet(plural.Other), 173 | PluralFunc: func(ops *plural.Operands) plural.Category { 174 | return plural.Other 175 | }, 176 | }, 177 | 178 | // Portuguese (European) 179 | "pt": &Language{ 180 | ID: "pt", 181 | PluralCategories: newSet(plural.One, plural.Other), 182 | PluralFunc: func(ops *plural.Operands) plural.Category { 183 | if ops.I == 1 && ops.V == 0 { 184 | return plural.One 185 | } 186 | return plural.Other 187 | }, 188 | }, 189 | 190 | // Portuguese (Brazilian) 191 | "pt-BR": &Language{ 192 | ID: "pt-BR", 193 | PluralCategories: newSet(plural.One, plural.Other), 194 | PluralFunc: func(ops *plural.Operands) plural.Category { 195 | if (ops.I == 1 && ops.V == 0) || (ops.I == 0 && ops.T == 1) { 196 | return plural.One 197 | } 198 | return plural.Other 199 | }, 200 | }, 201 | 202 | // Spanish 203 | "es": &Language{ 204 | ID: "es", 205 | PluralCategories: newSet(plural.One, plural.Other), 206 | PluralFunc: func(ops *plural.Operands) plural.Category { 207 | if ops.I == 1 && ops.W == 0 { 208 | return plural.One 209 | } 210 | return plural.Other 211 | }, 212 | }, 213 | } 214 | 215 | // LanguageWithID returns the language identified by id 216 | // or nil if the language is not registered. 217 | func LanguageWithID(id string) *Language { 218 | return languages[id] 219 | } 220 | 221 | // Register adds Language l to the collection of available languages. 222 | func Register(l *Language) { 223 | languages[l.ID] = l 224 | } 225 | 226 | // PluralCategory returns the plural category for number as defined by 227 | // the language's CLDR plural rules. 228 | func (l *Language) PluralCategory(number interface{}) (plural.Category, error) { 229 | ops, err := plural.NewOperands(number) 230 | if err != nil { 231 | return plural.Invalid, err 232 | } 233 | return l.PluralFunc(ops), nil 234 | } 235 | 236 | func (l *Language) String() string { 237 | return l.ID 238 | } 239 | 240 | func newSet(pluralCategories ...plural.Category) map[plural.Category]struct{} { 241 | set := make(map[plural.Category]struct{}, len(pluralCategories)) 242 | for _, pc := range pluralCategories { 243 | set[pc] = struct{}{} 244 | } 245 | return set 246 | } 247 | -------------------------------------------------------------------------------- /static/css/bootstrap-select.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * bootstrap-select v1.4.3 3 | * http://silviomoreto.github.io/bootstrap-select/ 4 | * 5 | * Copyright 2013 bootstrap-select 6 | * Licensed under the MIT license 7 | */ 8 | 9 | .bootstrap-select.btn-group, 10 | .bootstrap-select.btn-group[class*="span"] { 11 | float: none; 12 | display: inline-block; 13 | margin-bottom: 10px; 14 | margin-left: 0; 15 | } 16 | .form-search .bootstrap-select.btn-group, 17 | .form-inline .bootstrap-select.btn-group, 18 | .form-horizontal .bootstrap-select.btn-group { 19 | margin-bottom: 0; 20 | } 21 | 22 | .bootstrap-select.form-control { 23 | margin-bottom: 0; 24 | padding: 0; 25 | border: none; 26 | } 27 | 28 | .bootstrap-select.btn-group.pull-right, 29 | .bootstrap-select.btn-group[class*="span"].pull-right, 30 | .row-fluid .bootstrap-select.btn-group[class*="span"].pull-right { 31 | float: right; 32 | } 33 | 34 | .input-append .bootstrap-select.btn-group { 35 | margin-left: -1px; 36 | } 37 | 38 | .input-prepend .bootstrap-select.btn-group { 39 | margin-right: -1px; 40 | } 41 | 42 | .bootstrap-select:not([class*="span"]):not([class*="col-"]):not([class*="form-control"]) { 43 | width: 220px; 44 | } 45 | 46 | .bootstrap-select { 47 | /*width: 220px\9; IE8 and below*/ 48 | width: 220px\0; /*IE9 and below*/ 49 | } 50 | 51 | .bootstrap-select.form-control:not([class*="span"]) { 52 | width: 100%; 53 | } 54 | 55 | .bootstrap-select > .btn { 56 | width: 100%; 57 | } 58 | 59 | .error .bootstrap-select .btn { 60 | border: 1px solid #b94a48; 61 | } 62 | 63 | 64 | .dropdown-menu { 65 | z-index: 2000; 66 | } 67 | 68 | .bootstrap-select.show-menu-arrow.open > .btn { 69 | z-index: 2051; 70 | } 71 | 72 | .bootstrap-select .btn:focus { 73 | outline: thin dotted #333333 !important; 74 | outline: 5px auto -webkit-focus-ring-color !important; 75 | outline-offset: -2px; 76 | } 77 | 78 | .bootstrap-select.btn-group .btn .filter-option { 79 | overflow: hidden; 80 | position: absolute; 81 | left: 12px; 82 | right: 25px; 83 | text-align: left; 84 | } 85 | 86 | .bootstrap-select.btn-group .btn .caret { 87 | position: absolute; 88 | top: 50%; 89 | right: 12px; 90 | margin-top: -2px; 91 | vertical-align: middle; 92 | } 93 | 94 | .bootstrap-select.btn-group > .disabled, 95 | .bootstrap-select.btn-group .dropdown-menu li.disabled > a { 96 | cursor: not-allowed; 97 | } 98 | 99 | .bootstrap-select.btn-group > .disabled:focus { 100 | outline: none !important; 101 | } 102 | 103 | .bootstrap-select.btn-group[class*="span"] .btn { 104 | width: 100%; 105 | } 106 | 107 | .bootstrap-select.btn-group .dropdown-menu { 108 | min-width: 100%; 109 | -moz-box-sizing: border-box; 110 | -webkit-box-sizing: border-box; 111 | box-sizing: border-box; 112 | } 113 | 114 | .bootstrap-select.btn-group .dropdown-menu.inner { 115 | position: static; 116 | border: 0; 117 | padding: 0; 118 | margin: 0; 119 | -webkit-border-radius: 0; 120 | -moz-border-radius: 0; 121 | border-radius: 0; 122 | -webkit-box-shadow: none; 123 | -moz-box-shadow: none; 124 | box-shadow: none; 125 | } 126 | 127 | .bootstrap-select.btn-group .dropdown-menu dt { 128 | display: block; 129 | padding: 3px 20px; 130 | cursor: default; 131 | } 132 | 133 | .bootstrap-select.btn-group .div-contain { 134 | overflow: hidden; 135 | } 136 | 137 | .bootstrap-select.btn-group .dropdown-menu li { 138 | position: relative; 139 | } 140 | 141 | .bootstrap-select.btn-group .dropdown-menu li > a.opt { 142 | position: relative; 143 | padding-left: 35px; 144 | } 145 | 146 | .bootstrap-select.btn-group .dropdown-menu li > a { 147 | cursor: pointer; 148 | } 149 | 150 | .bootstrap-select.btn-group .dropdown-menu li > dt small { 151 | font-weight: normal; 152 | } 153 | 154 | .bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a i.check-mark { 155 | display: inline-block; 156 | position: absolute; 157 | right: 15px; 158 | margin-top: 2.5px; 159 | } 160 | 161 | .bootstrap-select.btn-group .dropdown-menu li a i.check-mark { 162 | display: none; 163 | } 164 | 165 | .bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text { 166 | margin-right: 34px; 167 | } 168 | 169 | .bootstrap-select.btn-group .dropdown-menu li small { 170 | padding-left: 0.5em; 171 | } 172 | 173 | .bootstrap-select.btn-group .dropdown-menu li:not(.disabled) > a:hover small, 174 | .bootstrap-select.btn-group .dropdown-menu li:not(.disabled) > a:focus small, 175 | .bootstrap-select.btn-group .dropdown-menu li.active:not(.disabled) > a small { 176 | color: #64b1d8; 177 | color: rgba(255,255,255,0.4); 178 | } 179 | 180 | .bootstrap-select.btn-group .dropdown-menu li > dt small { 181 | font-weight: normal; 182 | } 183 | 184 | .bootstrap-select.show-menu-arrow .dropdown-toggle:before { 185 | content: ''; 186 | display: inline-block; 187 | border-left: 7px solid transparent; 188 | border-right: 7px solid transparent; 189 | border-bottom: 7px solid #CCC; 190 | border-bottom-color: rgba(0, 0, 0, 0.2); 191 | position: absolute; 192 | bottom: -4px; 193 | left: 9px; 194 | display: none; 195 | } 196 | 197 | .bootstrap-select.show-menu-arrow .dropdown-toggle:after { 198 | content: ''; 199 | display: inline-block; 200 | border-left: 6px solid transparent; 201 | border-right: 6px solid transparent; 202 | border-bottom: 6px solid white; 203 | position: absolute; 204 | bottom: -4px; 205 | left: 10px; 206 | display: none; 207 | } 208 | 209 | .bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before { 210 | bottom: auto; 211 | top: -3px; 212 | border-top: 7px solid #ccc; 213 | border-bottom: 0; 214 | border-top-color: rgba(0, 0, 0, 0.2); 215 | } 216 | 217 | .bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after { 218 | bottom: auto; 219 | top: -3px; 220 | border-top: 6px solid #ffffff; 221 | border-bottom: 0; 222 | } 223 | 224 | .bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before { 225 | right: 12px; 226 | left: auto; 227 | } 228 | .bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after { 229 | right: 13px; 230 | left: auto; 231 | } 232 | 233 | .bootstrap-select.show-menu-arrow.open > .dropdown-toggle:before, 234 | .bootstrap-select.show-menu-arrow.open > .dropdown-toggle:after { 235 | display: block; 236 | } 237 | 238 | .bootstrap-select.btn-group .no-results { 239 | padding: 3px; 240 | background: #f5f5f5; 241 | margin: 0 5px; 242 | } 243 | 244 | .mobile-device { 245 | position: absolute; 246 | top: 0; 247 | left: 0; 248 | display: block !important; 249 | width: 100%; 250 | height: 100% !important; 251 | opacity: 0; 252 | } 253 | 254 | .bootstrap-select.fit-width { 255 | width: auto !important; 256 | } 257 | 258 | .bootstrap-select.btn-group.fit-width .btn .filter-option { 259 | position: static; 260 | } 261 | 262 | .bootstrap-select.btn-group.fit-width .btn .caret { 263 | position: static; 264 | top: auto; 265 | margin-top: -1px; 266 | } 267 | 268 | .control-group.error .bootstrap-select .dropdown-toggle{ 269 | border-color: #b94a48; 270 | } 271 | 272 | .bootstrap-select-searchbox { 273 | padding: 4px 8px; 274 | } 275 | 276 | .bootstrap-select-searchbox input { 277 | margin-bottom: 0; 278 | } 279 | -------------------------------------------------------------------------------- /go-i18n/i18n/language/language_test.go: -------------------------------------------------------------------------------- 1 | package language 2 | 3 | import ( 4 | "fmt" 5 | "github.com/goinggo/beego-mgo/go-i18n/i18n/plural" 6 | "testing" 7 | ) 8 | 9 | type pluralTest struct { 10 | num interface{} 11 | pc plural.Category 12 | } 13 | 14 | func TestArabic(t *testing.T) { 15 | tests := []pluralTest{ 16 | {0, plural.Zero}, 17 | {"0", plural.Zero}, 18 | {"0.0", plural.Zero}, 19 | {"0.00", plural.Zero}, 20 | {1, plural.One}, 21 | {"1", plural.One}, 22 | {"1.0", plural.One}, 23 | {"1.00", plural.One}, 24 | {2, plural.Two}, 25 | {"2", plural.Two}, 26 | {"2.0", plural.Two}, 27 | {"2.00", plural.Two}, 28 | {3, plural.Few}, 29 | {"3", plural.Few}, 30 | {"3.0", plural.Few}, 31 | {"3.00", plural.Few}, 32 | {10, plural.Few}, 33 | {"10", plural.Few}, 34 | {"10.0", plural.Few}, 35 | {"10.00", plural.Few}, 36 | {103, plural.Few}, 37 | {"103", plural.Few}, 38 | {"103.0", plural.Few}, 39 | {"103.00", plural.Few}, 40 | {110, plural.Few}, 41 | {"110", plural.Few}, 42 | {"110.0", plural.Few}, 43 | {"110.00", plural.Few}, 44 | {11, plural.Many}, 45 | {"11", plural.Many}, 46 | {"11.0", plural.Many}, 47 | {"11.00", plural.Many}, 48 | {99, plural.Many}, 49 | {"99", plural.Many}, 50 | {"99.0", plural.Many}, 51 | {"99.00", plural.Many}, 52 | {111, plural.Many}, 53 | {"111", plural.Many}, 54 | {"111.0", plural.Many}, 55 | {"111.00", plural.Many}, 56 | {199, plural.Many}, 57 | {"199", plural.Many}, 58 | {"199.0", plural.Many}, 59 | {"199.00", plural.Many}, 60 | {100, plural.Other}, 61 | {"100", plural.Other}, 62 | {"100.0", plural.Other}, 63 | {"100.00", plural.Other}, 64 | {102, plural.Other}, 65 | {"102", plural.Other}, 66 | {"102.0", plural.Other}, 67 | {"102.00", plural.Other}, 68 | {200, plural.Other}, 69 | {"200", plural.Other}, 70 | {"200.0", plural.Other}, 71 | {"200.00", plural.Other}, 72 | {202, plural.Other}, 73 | {"202", plural.Other}, 74 | {"202.0", plural.Other}, 75 | {"202.00", plural.Other}, 76 | } 77 | tests = appendFloatTests(tests, 0.1, 0.9, plural.Other) 78 | tests = appendFloatTests(tests, 1.1, 1.9, plural.Other) 79 | tests = appendFloatTests(tests, 2.1, 2.9, plural.Other) 80 | tests = appendFloatTests(tests, 3.1, 3.9, plural.Other) 81 | tests = appendFloatTests(tests, 4.1, 4.9, plural.Other) 82 | runTests(t, LanguageWithID("ar"), tests) 83 | } 84 | 85 | func TestCatalan(t *testing.T) { 86 | tests := []pluralTest{ 87 | {0, plural.Other}, 88 | {"0", plural.Other}, 89 | {1, plural.One}, 90 | {"1", plural.One}, 91 | {"1.0", plural.Other}, 92 | {2, plural.Other}, 93 | {"2", plural.Other}, 94 | } 95 | tests = appendIntTests(tests, 2, 10, plural.Other) 96 | tests = appendFloatTests(tests, 0, 10, plural.Other) 97 | runTests(t, LanguageWithID("ca"), tests) 98 | } 99 | 100 | func TestChinese(t *testing.T) { 101 | tests := appendIntTests(nil, 0, 10, plural.Other) 102 | tests = appendFloatTests(tests, 0, 10, plural.Other) 103 | runTests(t, LanguageWithID("zh"), tests) 104 | } 105 | 106 | func TestCzech(t *testing.T) { 107 | tests := []pluralTest{ 108 | {0, plural.Other}, 109 | {"0", plural.Other}, 110 | {1, plural.One}, 111 | {"1", plural.One}, 112 | {2, plural.Few}, 113 | {"2", plural.Few}, 114 | {3, plural.Few}, 115 | {"3", plural.Few}, 116 | {4, plural.Few}, 117 | {"4", plural.Few}, 118 | {5, plural.Other}, 119 | {"5", plural.Other}, 120 | } 121 | tests = appendFloatTests(tests, 0, 10, plural.Many) 122 | runTests(t, LanguageWithID("cs"), tests) 123 | } 124 | 125 | func TestDanish(t *testing.T) { 126 | tests := []pluralTest{ 127 | {0, plural.Other}, 128 | {1, plural.One}, 129 | {2, plural.Other}, 130 | } 131 | tests = appendFloatTests(tests, 0.1, 1.9, plural.One) 132 | tests = appendFloatTests(tests, 2.0, 10.0, plural.Other) 133 | runTests(t, LanguageWithID("da"), tests) 134 | } 135 | 136 | func TestDutch(t *testing.T) { 137 | tests := []pluralTest{ 138 | {0, plural.Other}, 139 | {1, plural.One}, 140 | {2, plural.Other}, 141 | } 142 | tests = appendFloatTests(tests, 0.0, 10.0, plural.Other) 143 | runTests(t, LanguageWithID("nl"), tests) 144 | } 145 | 146 | func TestEnglish(t *testing.T) { 147 | tests := []pluralTest{ 148 | {0, plural.Other}, 149 | {1, plural.One}, 150 | {2, plural.Other}, 151 | } 152 | tests = appendFloatTests(tests, 0.0, 10.0, plural.Other) 153 | runTests(t, LanguageWithID("en"), tests) 154 | } 155 | 156 | func TestFrench(t *testing.T) { 157 | tests := []pluralTest{ 158 | {0, plural.One}, 159 | {1, plural.One}, 160 | {2, plural.Other}, 161 | } 162 | tests = appendFloatTests(tests, 0.0, 1.9, plural.One) 163 | tests = appendFloatTests(tests, 2.0, 10.0, plural.Other) 164 | runTests(t, LanguageWithID("fr"), tests) 165 | } 166 | 167 | func TestGerman(t *testing.T) { 168 | tests := []pluralTest{ 169 | {0, plural.Other}, 170 | {1, plural.One}, 171 | {2, plural.Other}, 172 | } 173 | tests = appendFloatTests(tests, 0.0, 10.0, plural.Other) 174 | runTests(t, LanguageWithID("de"), tests) 175 | } 176 | 177 | func TestItalian(t *testing.T) { 178 | tests := []pluralTest{ 179 | {0, plural.Other}, 180 | {1, plural.One}, 181 | {2, plural.Other}, 182 | } 183 | tests = appendFloatTests(tests, 0.0, 10.0, plural.Other) 184 | runTests(t, LanguageWithID("it"), tests) 185 | } 186 | 187 | func TestJapanese(t *testing.T) { 188 | tests := appendIntTests(nil, 0, 10, plural.Other) 189 | tests = appendFloatTests(tests, 0, 10, plural.Other) 190 | runTests(t, LanguageWithID("ja"), tests) 191 | } 192 | 193 | func TestPortuguese(t *testing.T) { 194 | tests := []pluralTest{ 195 | {0, plural.Other}, 196 | {1, plural.One}, 197 | {2, plural.Other}, 198 | } 199 | tests = appendFloatTests(tests, 0.0, 10.0, plural.Other) 200 | runTests(t, LanguageWithID("pt"), tests) 201 | } 202 | 203 | func TestPortugueseBrazilian(t *testing.T) { 204 | tests := []pluralTest{ 205 | {0, plural.Other}, 206 | {"0.0", plural.Other}, 207 | {"0.1", plural.One}, 208 | {"0.01", plural.One}, 209 | {1, plural.One}, 210 | {"1", plural.One}, 211 | {"1.1", plural.Other}, 212 | {"1.01", plural.Other}, 213 | {2, plural.Other}, 214 | } 215 | tests = appendFloatTests(tests, 2.0, 10.0, plural.Other) 216 | runTests(t, LanguageWithID("pt-BR"), tests) 217 | } 218 | 219 | func TestSpanish(t *testing.T) { 220 | tests := []pluralTest{ 221 | {0, plural.Other}, 222 | {1, plural.One}, 223 | {"1", plural.One}, 224 | {"1.0", plural.One}, 225 | {"1.00", plural.One}, 226 | {2, plural.Other}, 227 | } 228 | tests = appendFloatTests(tests, 0.0, 0.9, plural.Other) 229 | tests = appendFloatTests(tests, 1.1, 10.0, plural.Other) 230 | runTests(t, LanguageWithID("es"), tests) 231 | } 232 | 233 | func appendIntTests(tests []pluralTest, from, to int, pc plural.Category) []pluralTest { 234 | for i := from; i <= to; i++ { 235 | tests = append(tests, pluralTest{i, pc}) 236 | } 237 | return tests 238 | } 239 | 240 | func appendFloatTests(tests []pluralTest, from, to float64, pc plural.Category) []pluralTest { 241 | stride := 0.1 242 | format := "%.1f" 243 | for f := from; f < to; f += stride { 244 | tests = append(tests, pluralTest{fmt.Sprintf(format, f), pc}) 245 | } 246 | tests = append(tests, pluralTest{fmt.Sprintf(format, to), pc}) 247 | return tests 248 | } 249 | 250 | func runTests(t *testing.T, language *Language, tests []pluralTest) { 251 | for _, test := range tests { 252 | if pc, err := language.PluralCategory(test.num); pc != test.pc { 253 | t.Errorf("PluralCategory(%#v) returned %s, %v; expected %s", test.num, pc, err, test.pc) 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /go-i18n/README.md: -------------------------------------------------------------------------------- 1 | go-i18n [![Build Status](https://secure.travis-ci.org/nicksnyder/go-i18n.png?branch=master)](http://travis-ci.org/nicksnyder/go-i18n) 2 | ======= 3 | 4 | go-i18n is a Go [package](#i18n-package) and a [command](#goi18n-command) that can be used to translate Go programs into multiple languages. 5 | 6 | Requires Go 1.2. 7 | 8 | Features 9 | -------- 10 | 11 | * Implements [CLDR plural rules](http://cldr.unicode.org/index/cldr-spec/plural-rules). 12 | * Uses [text/template](http://golang.org/pkg/text/template/) for strings with variables. 13 | * Translation files are simple JSON. 14 | * [Documented](http://godoc.org/github.com/nicksnyder/go-i18n) and [tested](https://travis-ci.org/nicksnyder/go-i18n)! 15 | 16 | i18n package 17 | ------------ 18 | 19 | The i18n package provides runtime APIs for looking up translated strings. 20 | 21 | ```go 22 | import "github.com/nicksnyder/go-i18n/i18n" 23 | ``` 24 | 25 | ##### Loading translations 26 | 27 | Load translation files during your program's initialization. 28 | The name of a translation file must contain a supported [language tag](http://en.wikipedia.org/wiki/IETF_language_tag). 29 | 30 | ```go 31 | i18n.MustLoadTranslationFile("path/to/fr-FR.all.json") 32 | ``` 33 | 34 | ##### Selecting a locale 35 | 36 | Tfunc returns a function that can lookup the translation of a string for that locale. 37 | It accepts one or more locale parameters so you can gracefully fallback to other locales. 38 | 39 | ```go 40 | userLocale = "ar-AR" // user preference, accept header, language cookie 41 | defaultLocale = "en-US" // known valid locale 42 | T, err := i18n.Tfunc(userLocale, defaultLocale) 43 | ``` 44 | 45 | ##### Loading a string translation 46 | 47 | Use the translation function to fetch the translation of a string. 48 | 49 | ```go 50 | fmt.Println(T("Hello world")) 51 | ``` 52 | 53 | Usually it is a good idea to identify strings by a generic id rather than the English translation, but the rest of this document will continue to use the English translation for readability. 54 | 55 | ```go 56 | T("program_greeting") 57 | ``` 58 | 59 | ##### Strings with variables 60 | 61 | You can have variables in your string using [text/template](http://golang.org/pkg/text/template/) syntax. 62 | 63 | ```go 64 | T("Hello {{.Person}}", map[string]interface{}{ 65 | "Person": "Bob", 66 | }) 67 | ``` 68 | 69 | ##### Plural strings 70 | 71 | Each language handles pluralization differently. A few examples: 72 | * English treats one as singular and all other numbers as plural (e.g. 0 cats, 1 cat, 2 cats). 73 | * French treats zero as singular. 74 | * Japan has a single plural form for all numbers. 75 | * Arabic has six different plural forms! 76 | 77 | The translation function handles [all of this logic](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) for you. 78 | 79 | ```go 80 | T("You have {{.Count}} unread emails", 2) 81 | T("I am {{.Count}} meters tall.", "1.7") 82 | ``` 83 | 84 | With variables: 85 | 86 | ```go 87 | T("{{.Person}} has {{.Count}} unread emails", 2, map[string]interface{}{ 88 | "Person": "Bob", 89 | }) 90 | ``` 91 | 92 | Sentences with multiple plural components can be supported with nesting. 93 | 94 | ```go 95 | T("{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 3, map[string]interface{}{ 96 | "Person": "Bob", 97 | "Timeframe": T("{{.Count}} days", 2), 98 | }) 99 | ``` 100 | 101 | A complete example is [here](i18n/example_test.go). 102 | 103 | ##### Strings in templates 104 | 105 | You can call the `.Funcs()` method on a [text/template](http://golang.org/pkg/text/template/#Template.Funcs) or [html/template](http://golang.org/pkg/html/template/#Template.Funcs) to register the translation function for usage inside of that template. 106 | 107 | A complete example is [here](i18n/exampletemplate_test.go). 108 | 109 | goi18n command 110 | -------------- 111 | 112 | The goi18n command provides functionality for managing the translation process. 113 | 114 | ### Installation 115 | 116 | Make sure you have [setup GOPATH](http://golang.org/doc/code.html#GOPATH). 117 | 118 | go get -u github.com/nicksnyder/go-i18n/goi18n 119 | goi18n -help 120 | 121 | ### Workflow 122 | 123 | A typical workflow looks like this: 124 | 125 | 1. Add a new string to your source code. 126 | 127 | ```go 128 | T("settings_title") 129 | ``` 130 | 131 | 2. Add the string to en-US.all.json 132 | 133 | ```json 134 | [ 135 | { 136 | "id": "settings_title", 137 | "translation": "Settings" 138 | } 139 | ] 140 | ``` 141 | 142 | 3. Run goi18n 143 | 144 | ``` 145 | goi18n path/to/*.all.json 146 | ``` 147 | 148 | 4. Send `path/to/*.untranslated.json` to get translated. 149 | 5. Run goi18n again to merge the translations 150 | 151 | ```sh 152 | goi18n path/to/*.all.json path/to/*.untranslated.json 153 | ``` 154 | 155 | Translation files 156 | ----------------- 157 | 158 | A translation file stores translated and untranslated strings. 159 | 160 | Example: 161 | 162 | ```json 163 | [ 164 | { 165 | "id": "d_days", 166 | "translation": { 167 | "one": "{{.Count}} day", 168 | "other": "{{.Count}} days" 169 | } 170 | }, 171 | { 172 | "id": "my_height_in_meters", 173 | "translation": { 174 | "one": "I am {{.Count}} meter tall.", 175 | "other": "I am {{.Count}} meters tall." 176 | } 177 | }, 178 | { 179 | "id": "person_greeting", 180 | "translation": "Hello {{.Person}}" 181 | }, 182 | { 183 | "id": "person_unread_email_count", 184 | "translation": { 185 | "one": "{{.Person}} has {{.Count}} unread email.", 186 | "other": "{{.Person}} has {{.Count}} unread emails." 187 | } 188 | }, 189 | { 190 | "id": "person_unread_email_count_timeframe", 191 | "translation": { 192 | "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", 193 | "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." 194 | } 195 | }, 196 | { 197 | "id": "program_greeting", 198 | "translation": "Hello world" 199 | }, 200 | { 201 | "id": "your_unread_email_count", 202 | "translation": { 203 | "one": "You have {{.Count}} unread email.", 204 | "other": "You have {{.Count}} unread emails." 205 | } 206 | } 207 | ] 208 | ``` 209 | 210 | Supported languages 211 | ------------------- 212 | 213 | * Arabic (`ar`) 214 | * Catalan (`ca`) 215 | * Chinese (simplified and traditional) (`zh`) 216 | * Czech (`cs`) 217 | * Danish (`da`) 218 | * Dutch (`nl`) 219 | * English (`en`) 220 | * French (`fr`) 221 | * German (`de`) 222 | * Italian (`it`) 223 | * Japanese (`ja`) 224 | * Portuguese (`pt`) 225 | * Portuguese (Brazilian) (`pt-BR`) 226 | * Spanish (`es`) 227 | 228 | More languages are straightforward to add: 229 | 230 | 1. Lookup the language's [CLDR plural rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html). 231 | 2. Add the language to [language.go](i18n/language/language.go): 232 | 233 | ```go 234 | var languages = map[string]*Language{ 235 | // ... 236 | "en": &Language{ 237 | ID: "en", 238 | Name: "English", 239 | PluralCategories: newSet(plural.One, plural.Other), 240 | PluralFunc: func(ops *plural.Operands) plural.Category { 241 | if ops.I == 1 && ops.V == 0 { 242 | return plural.One 243 | } 244 | return plural.Other 245 | }, 246 | }, 247 | // ... 248 | } 249 | ``` 250 | 251 | 3. Add a test to [language_test.go](i18n/language/language_test.go) 252 | 4. Submit a pull request! 253 | 254 | License 255 | ------- 256 | go-i18n is available under the MIT license. See the [LICENSE](LICENSE) file for more info. 257 | -------------------------------------------------------------------------------- /go-i18n/i18n/translation/plural_translation_test.go: -------------------------------------------------------------------------------- 1 | package translation 2 | 3 | import ( 4 | "github.com/goinggo/beego-mgo/go-i18n/i18n/plural" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func mustTemplate(t *testing.T, src string) *template { 10 | tmpl, err := newTemplate(src) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | return tmpl 15 | } 16 | 17 | func pluralTranslationFixture(t *testing.T, id string, pluralCategories ...plural.Category) *pluralTranslation { 18 | templates := make(map[plural.Category]*template, len(pluralCategories)) 19 | for _, pc := range pluralCategories { 20 | templates[pc] = mustTemplate(t, string(pc)) 21 | } 22 | return &pluralTranslation{id, templates} 23 | } 24 | 25 | func verifyDeepEqual(t *testing.T, actual, expected interface{}) { 26 | if !reflect.DeepEqual(actual, expected) { 27 | t.Fatalf("\n%#v\nnot equal to expected value\n%#v", actual, expected) 28 | } 29 | } 30 | 31 | func TestPluralTranslationMerge(t *testing.T) { 32 | pt := pluralTranslationFixture(t, "id", plural.One, plural.Other) 33 | oneTemplate, otherTemplate := pt.templates[plural.One], pt.templates[plural.Other] 34 | 35 | pt.Merge(pluralTranslationFixture(t, "id")) 36 | verifyDeepEqual(t, pt.templates, map[plural.Category]*template{ 37 | plural.One: oneTemplate, 38 | plural.Other: otherTemplate, 39 | }) 40 | 41 | pt2 := pluralTranslationFixture(t, "id", plural.One, plural.Two) 42 | pt.Merge(pt2) 43 | verifyDeepEqual(t, pt.templates, map[plural.Category]*template{ 44 | plural.One: pt2.templates[plural.One], 45 | plural.Two: pt2.templates[plural.Two], 46 | plural.Other: otherTemplate, 47 | }) 48 | } 49 | 50 | /* Test implementations from old idea 51 | 52 | func TestCopy(t *testing.T) { 53 | ls := &LocalizedString{ 54 | ID: "id", 55 | Translation: testingTemplate(t, "translation {{.Hello}}"), 56 | Translations: map[plural.Category]*template{ 57 | plural.One: testingTemplate(t, "plural {{.One}}"), 58 | plural.Other: testingTemplate(t, "plural {{.Other}}"), 59 | }, 60 | } 61 | 62 | c := ls.Copy() 63 | delete(c.Translations, plural.One) 64 | if _, ok := ls.Translations[plural.One]; !ok { 65 | t.Errorf("deleting plural translation from copy deleted it from the original") 66 | } 67 | c.Translations[plural.Two] = testingTemplate(t, "plural {{.Two}}") 68 | if _, ok := ls.Translations[plural.Two]; ok { 69 | t.Errorf("adding plural translation to copy added it to the original") 70 | } 71 | } 72 | 73 | func TestNormalize(t *testing.T) { 74 | oneTemplate := testingTemplate(t, "one {{.One}}") 75 | ls := &LocalizedString{ 76 | Translation: testingTemplate(t, "single {{.Single}}"), 77 | Translations: map[plural.Category]*template{ 78 | plural.One: oneTemplate, 79 | plural.Two: testingTemplate(t, "two {{.Two}}"), 80 | }, 81 | } 82 | ls.Normalize(LanguageWithCode("en")) 83 | if ls.Translation != nil { 84 | t.Errorf("ls.Translation is %#v; expected nil", ls.Translation) 85 | } 86 | if actual := ls.Translations[plural.Two]; actual != nil { 87 | t.Errorf("ls.Translation[plural.Two] is %#v; expected nil", actual) 88 | } 89 | if actual := ls.Translations[plural.One]; actual != oneTemplate { 90 | t.Errorf("ls.Translations[plural.One] is %#v; expected %#v", actual, oneTemplate) 91 | } 92 | if _, ok := ls.Translations[plural.Other]; !ok { 93 | t.Errorf("ls.Translations[plural.Other] shouldn't be empty") 94 | } 95 | } 96 | 97 | func TestMergeTranslation(t *testing.T) { 98 | ls := &LocalizedString{} 99 | 100 | translation := testingTemplate(t, "one {{.Hello}}") 101 | ls.Merge(&LocalizedString{ 102 | Translation: translation, 103 | }) 104 | if ls.Translation != translation { 105 | t.Errorf("expected %#v; got %#v", translation, ls.Translation) 106 | } 107 | 108 | ls.Merge(&LocalizedString{}) 109 | if ls.Translation != translation { 110 | t.Errorf("expected %#v; got %#v", translation, ls.Translation) 111 | } 112 | 113 | translation = testingTemplate(t, "two {{.Hello}}") 114 | ls.Merge(&LocalizedString{ 115 | Translation: translation, 116 | }) 117 | if ls.Translation != translation { 118 | t.Errorf("expected %#v; got %#v", translation, ls.Translation) 119 | } 120 | } 121 | 122 | func TestMergeTranslations(t *testing.T) { 123 | ls := &LocalizedString{} 124 | 125 | oneTemplate := testingTemplate(t, "one {{.One}}") 126 | otherTemplate := testingTemplate(t, "other {{.Other}}") 127 | ls.Merge(&LocalizedString{ 128 | Translations: map[plural.Category]*template{ 129 | plural.One: oneTemplate, 130 | plural.Other: otherTemplate, 131 | }, 132 | }) 133 | if actual := ls.Translations[plural.One]; actual != oneTemplate { 134 | t.Errorf("ls.Translations[plural.One] expected %#v; got %#v", oneTemplate, actual) 135 | } 136 | if actual := ls.Translations[plural.Other]; actual != otherTemplate { 137 | t.Errorf("ls.Translations[plural.Other] expected %#v; got %#v", otherTemplate, actual) 138 | } 139 | 140 | ls.Merge(&LocalizedString{ 141 | Translations: map[plural.Category]*template{}, 142 | }) 143 | if actual := ls.Translations[plural.One]; actual != oneTemplate { 144 | t.Errorf("ls.Translations[plural.One] expected %#v; got %#v", oneTemplate, actual) 145 | } 146 | if actual := ls.Translations[plural.Other]; actual != otherTemplate { 147 | t.Errorf("ls.Translations[plural.Other] expected %#v; got %#v", otherTemplate, actual) 148 | } 149 | 150 | twoTemplate := testingTemplate(t, "two {{.Two}}") 151 | otherTemplate = testingTemplate(t, "second other {{.Other}}") 152 | ls.Merge(&LocalizedString{ 153 | Translations: map[plural.Category]*template{ 154 | plural.Two: twoTemplate, 155 | plural.Other: otherTemplate, 156 | }, 157 | }) 158 | if actual := ls.Translations[plural.One]; actual != oneTemplate { 159 | t.Errorf("ls.Translations[plural.One] expected %#v; got %#v", oneTemplate, actual) 160 | } 161 | if actual := ls.Translations[plural.Two]; actual != twoTemplate { 162 | t.Errorf("ls.Translations[plural.Two] expected %#v; got %#v", twoTemplate, actual) 163 | } 164 | if actual := ls.Translations[plural.Other]; actual != otherTemplate { 165 | t.Errorf("ls.Translations[plural.Other] expected %#v; got %#v", otherTemplate, actual) 166 | } 167 | } 168 | 169 | func TestMissingTranslations(t *testing.T) { 170 | en := LanguageWithCode("en") 171 | 172 | tests := []struct { 173 | localizedString *LocalizedString 174 | language *Language 175 | expected bool 176 | }{ 177 | { 178 | &LocalizedString{}, 179 | en, 180 | true, 181 | }, 182 | { 183 | &LocalizedString{Translation: testingTemplate(t, "single {{.Single}}")}, 184 | en, 185 | false, 186 | }, 187 | { 188 | &LocalizedString{ 189 | Translation: testingTemplate(t, "single {{.Single}}"), 190 | Translations: map[plural.Category]*template{ 191 | plural.One: testingTemplate(t, "one {{.One}}"), 192 | }}, 193 | en, 194 | true, 195 | }, 196 | { 197 | &LocalizedString{Translations: map[plural.Category]*template{ 198 | plural.One: testingTemplate(t, "one {{.One}}"), 199 | }}, 200 | en, 201 | true, 202 | }, 203 | { 204 | &LocalizedString{Translations: map[plural.Category]*template{ 205 | plural.One: nil, 206 | plural.Other: nil, 207 | }}, 208 | en, 209 | true, 210 | }, 211 | { 212 | &LocalizedString{Translations: map[plural.Category]*template{ 213 | plural.One: testingTemplate(t, ""), 214 | plural.Other: testingTemplate(t, ""), 215 | }}, 216 | en, 217 | true, 218 | }, 219 | { 220 | &LocalizedString{Translations: map[plural.Category]*template{ 221 | plural.One: testingTemplate(t, "one {{.One}}"), 222 | plural.Other: testingTemplate(t, "other {{.Other}}"), 223 | }}, 224 | en, 225 | false, 226 | }, 227 | } 228 | 229 | for _, tt := range tests { 230 | if actual := tt.localizedString.MissingTranslations(tt.language); actual != tt.expected { 231 | t.Errorf("expected %t got %t for %s, %#v", 232 | tt.expected, actual, tt.language.code, tt.localizedString) 233 | } 234 | } 235 | } 236 | 237 | func TestHasTranslations(t *testing.T) { 238 | en := LanguageWithCode("en") 239 | 240 | tests := []struct { 241 | localizedString *LocalizedString 242 | language *Language 243 | expected bool 244 | }{ 245 | { 246 | &LocalizedString{}, 247 | en, 248 | false, 249 | }, 250 | { 251 | &LocalizedString{Translation: testingTemplate(t, "single {{.Single}}")}, 252 | en, 253 | true, 254 | }, 255 | { 256 | &LocalizedString{ 257 | Translation: testingTemplate(t, "single {{.Single}}"), 258 | Translations: map[plural.Category]*template{}}, 259 | en, 260 | false, 261 | }, 262 | { 263 | &LocalizedString{Translations: map[plural.Category]*template{ 264 | plural.One: testingTemplate(t, "one {{.One}}"), 265 | }}, 266 | en, 267 | true, 268 | }, 269 | { 270 | &LocalizedString{Translations: map[plural.Category]*template{ 271 | plural.Two: testingTemplate(t, "two {{.Two}}"), 272 | }}, 273 | en, 274 | false, 275 | }, 276 | { 277 | &LocalizedString{Translations: map[plural.Category]*template{ 278 | plural.One: nil, 279 | }}, 280 | en, 281 | false, 282 | }, 283 | { 284 | &LocalizedString{Translations: map[plural.Category]*template{ 285 | plural.One: testingTemplate(t, ""), 286 | }}, 287 | en, 288 | false, 289 | }, 290 | } 291 | 292 | for _, tt := range tests { 293 | if actual := tt.localizedString.HasTranslations(tt.language); actual != tt.expected { 294 | t.Errorf("expected %t got %t for %s, %#v", 295 | tt.expected, actual, tt.language.code, tt.localizedString) 296 | } 297 | } 298 | } 299 | 300 | func testingTemplate(t *testing.T, src string) *template { 301 | tmpl, err := newTemplate(src) 302 | if err != nil { 303 | t.Fatal(err) 304 | } 305 | return tmpl 306 | } 307 | */ 308 | -------------------------------------------------------------------------------- /utilities/mongo/mongo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Ardan Studios. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package mongo provides mongo connectivity support. 6 | package mongo 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "strings" 12 | "time" 13 | 14 | log "github.com/goinggo/tracelog" 15 | "github.com/kelseyhightower/envconfig" 16 | "gopkg.in/mgo.v2" 17 | "gopkg.in/mgo.v2/bson" 18 | ) 19 | 20 | const ( 21 | // MasterSession provides direct access to master database. 22 | MasterSession = "master" 23 | 24 | // MonotonicSession provides reads to slaves. 25 | MonotonicSession = "monotonic" 26 | ) 27 | 28 | var ( 29 | // Reference to the singleton. 30 | singleton mongoManager 31 | ) 32 | 33 | type ( 34 | // mongoConfiguration contains settings for initialization. 35 | mongoConfiguration struct { 36 | Hosts string 37 | Database string 38 | UserName string 39 | Password string 40 | } 41 | 42 | // mongoManager contains dial and session information. 43 | mongoSession struct { 44 | mongoDBDialInfo *mgo.DialInfo 45 | mongoSession *mgo.Session 46 | } 47 | 48 | // mongoManager manages a map of session. 49 | mongoManager struct { 50 | sessions map[string]mongoSession 51 | } 52 | 53 | // DBCall defines a type of function that can be used 54 | // to excecute code against MongoDB. 55 | DBCall func(*mgo.Collection) error 56 | ) 57 | 58 | // Startup brings the manager to a running state. 59 | func Startup(sessionID string) error { 60 | // If the system has already been started ignore the call. 61 | if singleton.sessions != nil { 62 | return nil 63 | } 64 | 65 | log.Started(sessionID, "Startup") 66 | 67 | // Pull in the configuration. 68 | var config mongoConfiguration 69 | if err := envconfig.Process("mgo", &config); err != nil { 70 | log.CompletedError(err, sessionID, "Startup") 71 | return err 72 | } 73 | 74 | // Create the Mongo Manager. 75 | singleton = mongoManager{ 76 | sessions: make(map[string]mongoSession), 77 | } 78 | 79 | // Log the mongodb connection straps. 80 | log.Trace(sessionID, "Startup", "MongoDB : Hosts[%s]", config.Hosts) 81 | log.Trace(sessionID, "Startup", "MongoDB : Database[%s]", config.Database) 82 | log.Trace(sessionID, "Startup", "MongoDB : Username[%s]", config.UserName) 83 | 84 | hosts := strings.Split(config.Hosts, ",") 85 | 86 | // Create the strong session. 87 | if err := CreateSession(sessionID, "strong", MasterSession, hosts, config.Database, config.UserName, config.Password); err != nil { 88 | log.CompletedError(err, sessionID, "Startup") 89 | return err 90 | } 91 | 92 | // Create the monotonic session. 93 | if err := CreateSession(sessionID, "monotonic", MonotonicSession, hosts, config.Database, config.UserName, config.Password); err != nil { 94 | log.CompletedError(err, sessionID, "Startup") 95 | return err 96 | } 97 | 98 | log.Completed(sessionID, "Startup") 99 | return nil 100 | } 101 | 102 | // Shutdown systematically brings the manager down gracefully. 103 | func Shutdown(sessionID string) error { 104 | log.Started(sessionID, "Shutdown") 105 | 106 | // Close the databases 107 | for _, session := range singleton.sessions { 108 | CloseSession(sessionID, session.mongoSession) 109 | } 110 | 111 | log.Completed(sessionID, "Shutdown") 112 | return nil 113 | } 114 | 115 | // CreateSession creates a connection pool for use. 116 | func CreateSession(sessionID string, mode string, sessionName string, hosts []string, databaseName string, username string, password string) error { 117 | log.Startedf(sessionID, "CreateSession", "Mode[%s] SessionName[%s] Hosts[%s] DatabaseName[%s] Username[%s]", mode, sessionName, hosts, databaseName, username) 118 | 119 | // Create the database object 120 | mongoSession := mongoSession{ 121 | mongoDBDialInfo: &mgo.DialInfo{ 122 | Addrs: hosts, 123 | Timeout: 60 * time.Second, 124 | Database: databaseName, 125 | Username: username, 126 | Password: password, 127 | }, 128 | } 129 | 130 | // Establish the master session. 131 | var err error 132 | mongoSession.mongoSession, err = mgo.DialWithInfo(mongoSession.mongoDBDialInfo) 133 | if err != nil { 134 | log.CompletedError(err, sessionID, "CreateSession") 135 | return err 136 | } 137 | 138 | switch mode { 139 | case "strong": 140 | // Reads and writes will always be made to the master server using a 141 | // unique connection so that reads and writes are fully consistent, 142 | // ordered, and observing the most up-to-date data. 143 | // http://godoc.org/github.com/finapps/mgo#Session.SetMode 144 | mongoSession.mongoSession.SetMode(mgo.Strong, true) 145 | break 146 | 147 | case "monotonic": 148 | // Reads may not be entirely up-to-date, but they will always see the 149 | // history of changes moving forward, the data read will be consistent 150 | // across sequential queries in the same session, and modifications made 151 | // within the session will be observed in following queries (read-your-writes). 152 | // http://godoc.org/github.com/finapps/mgo#Session.SetMode 153 | mongoSession.mongoSession.SetMode(mgo.Monotonic, true) 154 | } 155 | 156 | // Have the session check for errors. 157 | // http://godoc.org/github.com/finapps/mgo#Session.SetSafe 158 | mongoSession.mongoSession.SetSafe(&mgo.Safe{}) 159 | 160 | // Add the database to the map. 161 | singleton.sessions[sessionName] = mongoSession 162 | 163 | log.Completed(sessionID, "CreateSession") 164 | return nil 165 | } 166 | 167 | // CopyMasterSession makes a copy of the master session for client use. 168 | func CopyMasterSession(sessionID string) (*mgo.Session, error) { 169 | return CopySession(sessionID, MasterSession) 170 | } 171 | 172 | // CopyMonotonicSession makes a copy of the monotonic session for client use. 173 | func CopyMonotonicSession(sessionID string) (*mgo.Session, error) { 174 | return CopySession(sessionID, MonotonicSession) 175 | } 176 | 177 | // CopySession makes a copy of the specified session for client use. 178 | func CopySession(sessionID string, useSession string) (*mgo.Session, error) { 179 | log.Startedf(sessionID, "CopySession", "UseSession[%s]", useSession) 180 | 181 | // Find the session object. 182 | session := singleton.sessions[useSession] 183 | 184 | if session.mongoSession == nil { 185 | err := fmt.Errorf("Unable To Locate Session %s", useSession) 186 | log.CompletedError(err, sessionID, "CopySession") 187 | return nil, err 188 | } 189 | 190 | // Copy the master session. 191 | mongoSession := session.mongoSession.Copy() 192 | 193 | log.Completed(sessionID, "CopySession") 194 | return mongoSession, nil 195 | } 196 | 197 | // CloneMasterSession makes a clone of the master session for client use. 198 | func CloneMasterSession(sessionID string) (*mgo.Session, error) { 199 | return CloneSession(sessionID, MasterSession) 200 | } 201 | 202 | // CloneMonotonicSession makes a clone of the monotinic session for client use. 203 | func CloneMonotonicSession(sessionID string) (*mgo.Session, error) { 204 | return CloneSession(sessionID, MonotonicSession) 205 | } 206 | 207 | // CloneSession makes a clone of the specified session for client use. 208 | func CloneSession(sessionID string, useSession string) (*mgo.Session, error) { 209 | log.Startedf(sessionID, "CloneSession", "UseSession[%s]", useSession) 210 | 211 | // Find the session object. 212 | session := singleton.sessions[useSession] 213 | 214 | if session.mongoSession == nil { 215 | err := fmt.Errorf("Unable To Locate Session %s", useSession) 216 | log.CompletedError(err, sessionID, "CloneSession") 217 | return nil, err 218 | } 219 | 220 | // Clone the master session. 221 | mongoSession := session.mongoSession.Clone() 222 | 223 | log.Completed(sessionID, "CloneSession") 224 | return mongoSession, nil 225 | } 226 | 227 | // CloseSession puts the connection back into the pool. 228 | func CloseSession(sessionID string, mongoSession *mgo.Session) { 229 | log.Started(sessionID, "CloseSession") 230 | mongoSession.Close() 231 | log.Completed(sessionID, "CloseSession") 232 | } 233 | 234 | // GetDatabase returns a reference to the specified database. 235 | func GetDatabase(mongoSession *mgo.Session, useDatabase string) *mgo.Database { 236 | return mongoSession.DB(useDatabase) 237 | } 238 | 239 | // GetCollection returns a reference to a collection for the specified database and collection name. 240 | func GetCollection(mongoSession *mgo.Session, useDatabase string, useCollection string) *mgo.Collection { 241 | return mongoSession.DB(useDatabase).C(useCollection) 242 | } 243 | 244 | // CollectionExists returns true if the collection name exists in the specified database. 245 | func CollectionExists(sessionID string, mongoSession *mgo.Session, useDatabase string, useCollection string) bool { 246 | database := mongoSession.DB(useDatabase) 247 | collections, err := database.CollectionNames() 248 | 249 | if err != nil { 250 | return false 251 | } 252 | 253 | for _, collection := range collections { 254 | if collection == useCollection { 255 | return true 256 | } 257 | } 258 | 259 | return false 260 | } 261 | 262 | // ToString converts the quer map to a string. 263 | func ToString(queryMap interface{}) string { 264 | json, err := json.Marshal(queryMap) 265 | if err != nil { 266 | return "" 267 | } 268 | 269 | return string(json) 270 | } 271 | 272 | // ToStringD converts bson.D to a string. 273 | func ToStringD(queryMap bson.D) string { 274 | json, err := json.Marshal(queryMap) 275 | if err != nil { 276 | return "" 277 | } 278 | 279 | return string(json) 280 | } 281 | 282 | // Execute the MongoDB literal function. 283 | func Execute(sessionID string, mongoSession *mgo.Session, databaseName string, collectionName string, dbCall DBCall) error { 284 | log.Startedf(sessionID, "Execute", "Database[%s] Collection[%s]", databaseName, collectionName) 285 | 286 | // Capture the specified collection. 287 | collection := GetCollection(mongoSession, databaseName, collectionName) 288 | if collection == nil { 289 | err := fmt.Errorf("Collection %s does not exist", collectionName) 290 | log.CompletedError(err, sessionID, "Execute") 291 | return err 292 | } 293 | 294 | // Execute the MongoDB call. 295 | err := dbCall(collection) 296 | if err != nil { 297 | log.CompletedError(err, sessionID, "Execute") 298 | return err 299 | } 300 | 301 | log.Completed(sessionID, "Execute") 302 | return nil 303 | } 304 | -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.3 (http://getbootstrap.com) 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | 7 | if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]'),b=!0;if(a.length){var c=this.$element.find("input");"radio"===c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?b=!1:a.find(".active").removeClass("active")),b&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}b&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); --------------------------------------------------------------------------------