diff --git a/goloc/formats.go b/goloc/formats.go index 8129ed8..7446a34 100644 --- a/goloc/formats.go +++ b/goloc/formats.go @@ -8,7 +8,7 @@ import ( // ParseFormats parses formats given the raw table data and returns, if successful, mappings // to the actual platform format for each format name. func ParseFormats( - rawData [][]interface{}, + rawData [][]RawCell, platform Platform, formatsTabName string, formatColumnTitle string, @@ -33,35 +33,27 @@ func ParseFormats( platformName: actualPlatformName, } } - if key, ok := row[formatColIndex].(FormatKey); ok { - if val, ok := row[platformColIndex].(string); ok { - trimmedVal := strings.TrimSpace(val) - if len(trimmedVal) == 0 { - return nil, &formatValueNotSpecifiedError{ - cell: *NewCell(formatsTabName, actualRowIndex, uint(platformColIndex)), - platformName: actualPlatformName, - } - } - err := platform.ValidateFormat(trimmedVal) - if err != nil { - return nil, &formatValueInvalidError{ - cell: *NewCell(formatsTabName, actualRowIndex, uint(platformColIndex)), - platformName: actualPlatformName, - formatValue: trimmedVal, - reason: err, - } - } - formats[key] = trimmedVal - } else { - return nil, &wrongValueTypeError{ - cell: *NewCell(formatsTabName, actualRowIndex, uint(platformColIndex)), - } + + key := row[formatColIndex] + val := row[platformColIndex] + + trimmedVal := strings.TrimSpace(val) + if len(trimmedVal) == 0 { + return nil, &formatValueNotSpecifiedError{ + cell: *NewCell(formatsTabName, actualRowIndex, uint(platformColIndex)), + platformName: actualPlatformName, } - } else { - return nil, &wrongKeyTypeError{ - cell: *NewCell(formatsTabName, actualRowIndex, uint(formatColIndex)), + } + err := platform.ValidateFormat(trimmedVal) + if err != nil { + return nil, &formatValueInvalidError{ + cell: *NewCell(formatsTabName, actualRowIndex, uint(platformColIndex)), + platformName: actualPlatformName, + formatValue: trimmedVal, + reason: err, } } + formats[key] = trimmedVal } // Handle default format ("{}") @@ -74,7 +66,7 @@ func ParseFormats( func columnIndices( platform Platform, - rawData [][]interface{}, + rawData [][]RawCell, formatsTabName string, formatColumnTitle string, ) (formatColIndex int, platformColIndex int, actualPlatformName string, err error) { diff --git a/goloc/formats_test.go b/goloc/formats_test.go index 8e71d23..695af8f 100644 --- a/goloc/formats_test.go +++ b/goloc/formats_test.go @@ -7,7 +7,7 @@ import ( ) func TestFormatsEmptyData(t *testing.T) { - var data [][]interface{} + var data [][]RawCell platform := newMockPlatform(nil) _, err := ParseFormats(data, platform, "", "", "{}") @@ -17,7 +17,7 @@ func TestFormatsEmptyData(t *testing.T) { } func TestFormatsEmptyFirstRow(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {}, {"x"}, } @@ -30,7 +30,7 @@ func TestFormatsEmptyFirstRow(t *testing.T) { } func TestFormatsMissingFormatColumn(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"mock"}, } @@ -42,7 +42,7 @@ func TestFormatsMissingFormatColumn(t *testing.T) { } func TestFormatsMissingPlatformColumn(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"format"}, } @@ -54,7 +54,7 @@ func TestFormatsMissingPlatformColumn(t *testing.T) { } func TestFormatsMissingFormatKey(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"mock", "format"}, {""}, } @@ -67,7 +67,7 @@ func TestFormatsMissingFormatKey(t *testing.T) { } func TestFormatsMissingFormatValue1(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"format", "mock"}, {""}, } @@ -80,7 +80,7 @@ func TestFormatsMissingFormatValue1(t *testing.T) { } func TestFormatsMissingFormatValue2(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"format", "mock"}, {"x", ""}, } @@ -93,7 +93,7 @@ func TestFormatsMissingFormatValue2(t *testing.T) { } func TestFormatsMissingFormatValue3(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"format", "mock"}, {"x", " "}, } @@ -106,7 +106,7 @@ func TestFormatsMissingFormatValue3(t *testing.T) { } func TestFormatsFormatValidation(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"format", "mock"}, {"x", "s"}, {"y", "%s"}, @@ -126,44 +126,4 @@ func TestFormatsFormatValidation(t *testing.T) { platform.AssertCalled(t, "ValidateFormat", "s") platform.AssertCalled(t, "ValidateFormat", "%s") platform.AssertNumberOfCalls(t, "ValidateFormat", 2) -} - -func TestFormatsWrongValueType(t *testing.T) { - data := [][]interface{}{ - {"format", "mock"}, - {"x", "s"}, - {"y", 1}, - {"z", "%s"}, - } - - platform := newMockPlatform(nil) - - _, err := ParseFormats(data, platform, "", "format", "{}") - if assert.Error(t, err) { - assert.IsType(t, &wrongValueTypeError{}, err) - } - - platform.AssertCalled(t, "ValidateFormat", "s") - platform.AssertNotCalled(t, "ValidateFormat", "%s") - platform.AssertNumberOfCalls(t, "ValidateFormat", 1) -} - -func TestFormatsWrongKeyType(t *testing.T) { - data := [][]interface{}{ - {"format", "mock"}, - {"x", "s"}, - {1, "%s"}, - {"z", "%s"}, - } - - platform := newMockPlatform(nil) - - _, err := ParseFormats(data, platform, "", "format", "{}") - if assert.Error(t, err) { - assert.IsType(t, &wrongKeyTypeError{}, err) - } - - platform.AssertCalled(t, "ValidateFormat", "s") - platform.AssertNotCalled(t, "ValidateFormat", "%s") - platform.AssertNumberOfCalls(t, "ValidateFormat", 1) -} +} \ No newline at end of file diff --git a/goloc/goloc.go b/goloc/goloc.go index 1a09c0d..f062205 100644 --- a/goloc/goloc.go +++ b/goloc/goloc.go @@ -1,7 +1,7 @@ package goloc import ( - "io/ioutil" + "fmt" "log" "os" "regexp" @@ -11,11 +11,10 @@ import ( "sync" "github.com/olekukonko/tablewriter" - "golang.org/x/net/context" - "golang.org/x/oauth2/google" - "google.golang.org/api/sheets/v4" ) +type RawCell = string + // Lang represents a language code. type Lang = string @@ -37,62 +36,28 @@ type Formats = map[FormatKey]string // ResDir represents a resources directory path. type ResDir = string -func sheetsAPI(credFilePath string) *sheets.SpreadsheetsService { - ctx := context.Background() - - sec, err := ioutil.ReadFile(credFilePath) - if err != nil { - log.Fatalf("Unable to read client secret file: %v", err) - } - - config, err := google.JWTConfigFromJSON(sec, "https://www.googleapis.com/auth/spreadsheets.readonly") - if err != nil { - log.Fatalf("Unable to parse client secret file to config: %v", err) - } - - s, err := sheets.New(config.Client(ctx)) - if err != nil { - log.Fatalf("Unable to retrieve Sheets Client %v", err) - } - - return s.Spreadsheets -} - -func fetchRawValues(api *sheets.SpreadsheetsService, sheetID string, tab string) ([][]interface{}, error) { - resp, err := api.Values.Get(sheetID, tab).Do() - if err != nil { - return nil, err - } - return resp.Values, nil -} - -func fetchEverythingRaw( - api *sheets.SpreadsheetsService, - sheetID string, - formatsTab string, - localizationsTab string, -) (rawFormats, rawLocalizations [][]interface{}, err error) { +func fetchEverythingRaw(source Source) (rawFormats, rawLocalizations [][]string, err error) { var formatsError error var localizationsError error var wg sync.WaitGroup go func() { defer wg.Done() - rawFormats, formatsError = fetchRawValues(api, sheetID, formatsTab) + rawFormats, formatsError = source.Formats() }() go func() { defer wg.Done() - rawLocalizations, localizationsError = fetchRawValues(api, sheetID, localizationsTab) + rawLocalizations, localizationsError = source.Localizations() }() wg.Add(2) wg.Wait() if formatsError != nil { - return nil, nil, formatsError + return nil, nil, fmt.Errorf(`can't load formats (%v)`, formatsError) } if localizationsError != nil { - return nil, nil, localizationsError + return nil, nil, fmt.Errorf(`can't load localizations (%v)`, formatsError) } return @@ -100,13 +65,10 @@ func fetchEverythingRaw( // Run launches the actual process of fetching, parsing and writing the localization files. func Run( + source Source, platform Platform, resDir string, - credFilePath string, - sheetID string, - tabName string, keyColumn string, - formatsTabName string, formatNameColumn string, defaultLocalization string, defaultLocalizationPath string, @@ -115,19 +77,17 @@ func Run( defFormatName string, emptyLocalizationMatch *regexp.Regexp, ) { - api := sheetsAPI(credFilePath) - - rawFormats, rawLocalizations, err := fetchEverythingRaw(api, sheetID, formatsTabName, tabName) + rawFormats, rawLocalizations, err := fetchEverythingRaw(source) if err != nil { - log.Fatalf(`Can't fetch data from "%v" sheet. Reason: %v.`, sheetID, err) + log.Fatalf(`Can't fetch data. Reason: %v.`, err) } - formats, err := ParseFormats(rawFormats, platform, formatsTabName, formatNameColumn, defFormatName) + formats, err := ParseFormats(rawFormats, platform, source.FormatsDocumentName(), formatNameColumn, defFormatName) if err != nil { log.Fatal(err) } - localizations, fArgs, warn, err := ParseLocalizations(rawLocalizations, platform, formats, tabName, keyColumn, stopOnMissing, emptyLocalizationMatch) + localizations, fArgs, warn, err := ParseLocalizations(rawLocalizations, platform, formats, source.LocalizationsDocumentName(), keyColumn, stopOnMissing, emptyLocalizationMatch) if err != nil { log.Fatal(err) } else { diff --git a/goloc/localizations.go b/goloc/localizations.go index 874fef4..28e1749 100644 --- a/goloc/localizations.go +++ b/goloc/localizations.go @@ -17,7 +17,7 @@ var DefaultEmptyLocRegexp, _ = regexp.Compile("^$") // ParseLocalizations parses formats given the raw table data and returns, if successful, mappings // for each localized string in different languages. func ParseLocalizations( - rawData [][]interface{}, + rawData [][]RawCell, platform Platform, formats Formats, tabName string, @@ -27,7 +27,7 @@ func ParseLocalizations( ) (loc Localizations, formatArgs LocalizationFormatArgs, warnings []error, error error) { formatArgs = LocalizationFormatArgs{} - if (emptyLocalizationRegexp == nil) { + if emptyLocalizationRegexp == nil { emptyLocalizationRegexp = DefaultEmptyLocRegexp } @@ -40,7 +40,7 @@ func ParseLocalizations( loc = Localizations{} for index, row := range rawData[1:] { actualRow := index + 2 - if keyColIndex >= len(row) || len(strings.TrimSpace(row[keyColIndex].(Key))) == 0 { + if keyColIndex >= len(row) || len(strings.TrimSpace(row[keyColIndex])) == 0 { if errorIfMissing { error = newKeyMissingError(tabName, actualRow, keyColIndex) return @@ -48,7 +48,7 @@ func ParseLocalizations( warnings = append(warnings, newKeyMissingError(tabName, actualRow, keyColIndex)) continue } - key := strings.TrimSpace(row[keyColIndex].(Key)) + key := strings.TrimSpace(row[keyColIndex]) if keyLoc, warn, err := keyLocalizations(platform, formats, tabName, actualRow, row, key, langCols, errorIfMissing, emptyLocalizationRegexp); err == nil { if len(warn) > 0 { warnings = append(warnings, warn...) @@ -69,7 +69,7 @@ func ParseLocalizations( } func localizationColumnIndices( - rawData [][]interface{}, + rawData [][]RawCell, tabName string, keyColumn string, ) (keyColIndex int, langCols langColumns, err error) { @@ -91,7 +91,7 @@ func localizationColumnIndices( if val == keyColumn { keyColIndex = i } - lang := re.LangColumnNameRegexp().FindStringSubmatch(val.(string)) + lang := re.LangColumnNameRegexp().FindStringSubmatch(val) if lang != nil { langCols[i] = lang[1] } @@ -114,7 +114,7 @@ func keyFormatArgs( platform Platform, tab string, line int, - row []interface{}, + row []string, key Key, langColumns langColumns, emptyLocalizationRegexp *regexp.Regexp, @@ -123,7 +123,7 @@ func keyFormatArgs( for i := range langColumns { if i < len(row) { - val := strings.TrimSpace(row[i].(string)) + val := strings.TrimSpace(row[i]) valWithoutSpecChars := withReplacedSpecialChars(platform, val) if !emptyLocalizationRegexp.MatchString(valWithoutSpecChars) { langFormatArgs[i] = FormatArgs(valWithoutSpecChars) @@ -159,7 +159,7 @@ func keyLocalizations( formats Formats, tab string, line int, - row []interface{}, + row []string, key Key, langColumns langColumns, errorIfMissing bool, @@ -169,7 +169,7 @@ func keyLocalizations( for i, lang := range langColumns { keyLoc[lang] = "" if i < len(row) { - val := strings.TrimSpace(row[i].(string)) + val := strings.TrimSpace(row[i]) if match := emptyLocalizationRegexp.MatchString(val); !match { valWithoutSpecChars := withReplacedSpecialChars(platform, val) finalValue, err := withReplacedFormats(platform, valWithoutSpecChars, formats, tab, line, i) diff --git a/goloc/localizations_test.go b/goloc/localizations_test.go index 34020a0..ccc60e5 100644 --- a/goloc/localizations_test.go +++ b/goloc/localizations_test.go @@ -8,7 +8,7 @@ import ( ) func formats() Formats { - data := [][]interface{}{ + data := [][]RawCell{ {"format", "mock"}, {"x", "s"}, {"y", "%s"}, @@ -20,14 +20,14 @@ func formats() Formats { return formats } -func parseTestLocalizations(data [][]interface{}, errorIfMissing bool, missingRegexp *regexp.Regexp) (loc Localizations, warnings []error, error error) { +func parseTestLocalizations(data [][]RawCell, errorIfMissing bool, missingRegexp *regexp.Regexp) (loc Localizations, warnings []error, error error) { p := newMockPlatform(nil) loc, _, warnings, error = ParseLocalizations(data, p, formats(), "", "key", errorIfMissing, missingRegexp) return } func TestLocalizationsEmptyData(t *testing.T) { - var data [][]interface{} + var data [][]RawCell _, _, err := parseTestLocalizations(data, true, nil) assert.Error(t, err) @@ -35,7 +35,7 @@ func TestLocalizationsEmptyData(t *testing.T) { } func TestLocalizationsEmptyFirstRow(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {}, {"x"}, } @@ -45,7 +45,7 @@ func TestLocalizationsEmptyFirstRow(t *testing.T) { } func TestLocalizationsNoKeyColumn(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"", "lang_en"}, } @@ -55,7 +55,7 @@ func TestLocalizationsNoKeyColumn(t *testing.T) { } func TestLocalizationsNoLangColumns(t *testing.T) { - data := [][]interface{}{ + data := [][]RawCell{ {"key", "something", "something else"}, } @@ -65,7 +65,7 @@ func TestLocalizationsNoLangColumns(t *testing.T) { } func TestLocalizationsMissingKey(t *testing.T) { - dataBad := [][][]interface{}{ + dataBad := [][][]RawCell{ { {"key", "lang_en"}, {"", "something"}, @@ -88,7 +88,7 @@ func TestLocalizationsMissingKey(t *testing.T) { }, } - dataGood := [][][]interface{}{ + dataGood := [][][]RawCell{ { {"key", "lang_en"}, {"k", "something"}, @@ -126,7 +126,7 @@ func TestLocalizationsMissingKey(t *testing.T) { } func TestLocalizationsMissingFormat(t *testing.T) { - dataBad := [][][]interface{}{ + dataBad := [][][]RawCell{ { {"key", "lang_en"}, {"p1", "something {x}"}, @@ -136,7 +136,7 @@ func TestLocalizationsMissingFormat(t *testing.T) { }, } - dataGood := [][][]interface{}{ + dataGood := [][][]RawCell{ { {"key", "lang_en"}, {"p1", "something {x}"}, @@ -159,7 +159,7 @@ func TestLocalizationsMissingFormat(t *testing.T) { } func TestLocalizationsMissingLocalization(t *testing.T) { - dataBad := [][][]interface{}{ + dataBad := [][][]RawCell{ { {"key", "lang_en", "lang_ru"}, {"m", "something {y}"}, @@ -182,7 +182,7 @@ func TestLocalizationsMissingLocalization(t *testing.T) { }, } - dataGood := [][][]interface{}{ + dataGood := [][][]RawCell{ { {"key", "lang_en", "lang_ru"}, {"p", "something {x}", "что-то {x}"}, @@ -210,7 +210,7 @@ func TestLocalizationsMissingLocalization(t *testing.T) { func TestLocalizationsMissingRegexpLocalization(t *testing.T) { re, _ := regexp.Compile("^x$") - dataBad := [][][]interface{}{ + dataBad := [][][]RawCell{ { {"key", "lang_en"}, {"m", "x"}, @@ -221,7 +221,7 @@ func TestLocalizationsMissingRegexpLocalization(t *testing.T) { }, } - dataGood := [][][]interface{}{ + dataGood := [][][]RawCell{ { {"key", "lang_en"}, {"m", "xy"}, diff --git a/goloc/source.go b/goloc/source.go new file mode 100644 index 0000000..80e6a92 --- /dev/null +++ b/goloc/source.go @@ -0,0 +1,9 @@ +package goloc + +type Source interface { + FormatsDocumentName() string + LocalizationsDocumentName() string + + Formats() ([][]RawCell, error) + Localizations() ([][]RawCell, error) +} \ No newline at end of file diff --git a/main.go b/main.go index e7d85fa..d0d2d4e 100644 --- a/main.go +++ b/main.go @@ -1,37 +1,98 @@ package main import ( + "fmt" "github.com/s0nerik/goloc/goloc" - "github.com/s0nerik/goloc/platforms/resolver" + "github.com/s0nerik/goloc/registry" + "github.com/s0nerik/goloc/sources" "gopkg.in/alecthomas/kingpin.v2" "log" + + // Register all supported platforms + _ "github.com/s0nerik/goloc/platforms" ) +const remoteSources = `google_sheets` +const localSources = `csv` + +var availableSources = fmt.Sprintf(`%v, %v`, remoteSources, localSources) + var ( - credentials = kingpin.Flag(`credentials`, `Credentials to access a spreadsheet.`).Short('c').Default(`client_secret.json`).String() - platformName = kingpin.Flag(`platform`, `Target platform name.`).Short('p').Required().String() - sheetID = kingpin.Flag(`spreadsheet`, `Spreadsheet ID.`).Short('s').Required().String() - resDir = kingpin.Flag(`resources`, `Path to the resources folder in the project.`).Short('r').Required().String() - tabName = kingpin.Flag(`tab`, `Localizations tab name.`).Short('t').Required().String() - keyColumn = kingpin.Flag(`key-column`, `Title of the key column.`).Default(`key`).String() - stopOnMissing = kingpin.Flag(`stop-on-missing`, `Stop execution if missing localization is found.`).Default(`false`).Bool() - formatsTabName = kingpin.Flag(`formats-tab`, `Formats tab name.`).Short('f').Default(`formats`).String() - formatNameColumn = kingpin.Flag(`format-name-column`, `Title of the format name column.`).Default(`format`).String() - defFormatName = kingpin.Flag(`default-format-name`, `Name of the format to be used in place of "{}"`).Default("").String() - defLoc = kingpin.Flag(`default-localization`, `Default localization language (e.g. "en"). Specifying this doesn't have any effect if the "--default-localization-file-path" is not specified.`).Default(`en`).String() - defLocPath = kingpin.Flag(`default-localization-file-path`, `Full path to the default localization file. Specify this if you want to write a default localization into a specific file (ignoring the localization path generation logic for a language specified in "--default-localization").`).String() + // Basic params + source = kingpin.Flag(`source`, fmt.Sprintf(`Data source. Available sources: %v`, availableSources)).Default(`google_sheets`).String() + platformName = kingpin.Flag(`platform`, `Target platform name.`).Short('p').Required().String() + resDir = kingpin.Flag(`resources`, `Path to the resources folder in the project.`).Short('r').Required().String() + + // Local source params + locFilePath = kingpin.Flag(`localizations-file-path`, fmt.Sprintf(`Localizations file path. Required for sources: %v`, localSources)).String() + formatsFilePath = kingpin.Flag(`formats-file-path`, fmt.Sprintf(`Formats file path. Required for sources: %v`, localSources)).String() + + // Google Sheets params + sheetID = kingpin.Flag(`spreadsheet`, `Spreadsheet ID. Required if selected source is 'google_sheets'`).Short('s').String() + credentials = kingpin.Flag(`credentials`, `Credentials to access a spreadsheet.`).Short('c').Default(`client_secret.json`).String() + tabName = kingpin.Flag(`tab`, `Localizations tab name.`).Short('t').Default(`localizations`).String() + formatsTabName = kingpin.Flag(`formats-tab`, `Formats tab name.`).Short('f').Default(`formats`).String() + + // Advanced configuration + keyColumn = kingpin.Flag(`key-column`, `Title of the key column.`).Default(`key`).String() + stopOnMissing = kingpin.Flag(`stop-on-missing`, `Stop execution if missing localization is found.`).Default(`false`).Bool() + formatNameColumn = kingpin.Flag(`format-name-column`, `Title of the format name column.`).Default(`format`).String() + defFormatName = kingpin.Flag(`default-format-name`, `Name of the format to be used in place of "{}"`).Default("").String() + defLoc = kingpin.Flag(`default-localization`, `Default localization language (e.g. "en"). Specifying this doesn't have any effect if the "--default-localization-file-path" is not specified.`).Default(`en`).String() + defLocPath = kingpin.Flag(`default-localization-file-path`, `Full path to the default localization file. Specify this if you want to write a default localization into a specific file (ignoring the localization path generation logic for a language specified in "--default-localization").`).String() + emptyLocalizationMatch = kingpin.Flag(`empty-localization-match`, `Regex for empty localization string.`).Default(`^$`).Regexp() + + // Extra features missingLocalizationsReport = kingpin.Flag(`missing-localizations-report`, `Specify this flag if you want to only pretty-print missing localizations without generating the actual localization files.`).Default(`false`).Bool() - emptyLocalizationMatch = kingpin.Flag(`empty-localization-match`, `Regex for empty localization string.`).Default(`^$`).Regexp() ) func main() { - kingpin.Version("0.9.7") + kingpin.Version("0.9.8") kingpin.Parse() - platform, err := resolver.FindPlatform(*platformName) - if err != nil { + platform := registry.GetPlatform(*platformName) + if platform == nil { log.Fatalf(`Platform "%v" is not supported.`, *platformName) } - goloc.Run(platform, *resDir, *credentials, *sheetID, *tabName, *keyColumn, *formatsTabName, *formatNameColumn, *defLoc, *defLocPath, *stopOnMissing, *missingLocalizationsReport, *defFormatName, *emptyLocalizationMatch) + src := resolveSource() + if src == nil { + log.Fatalf(`"%v" is not a supported source. Supported sources: %v.`, *source, availableSources) + } + + goloc.Run(src, platform, *resDir, *keyColumn, *formatNameColumn, *defLoc, *defLocPath, *stopOnMissing, *missingLocalizationsReport, *defFormatName, *emptyLocalizationMatch) +} + +func resolveSource() goloc.Source { + switch *source { + case "google_sheets": + if sheetID == nil { + log.Fatalf(`"--spreadsheet" parameter must be specified`) + } + if credentials == nil { + log.Fatalf(`"--credentials" parameter must be specified`) + } + if *tabName == "" { + log.Fatalf(`"--tab" parameter cannot be empty`) + } + if *formatsTabName == "" { + log.Fatalf(`"--formats-tab" parameter cannot be empty`) + } + return sources.GoogleSheets(*credentials, *sheetID, *formatsTabName, *tabName) + case "csv": + if locFilePath == nil { + log.Fatalf(`"--localizations-file-path" parameter must be specified`) + } + if *locFilePath == "" { + log.Fatalf(`"--localizations-file-path" must be a valid file path`) + } + if formatsFilePath == nil { + log.Fatalf(`"--formats-file-path" parameter must be specified`) + } + if *formatsFilePath == "" { + log.Fatalf(`"--formats-file-path" must be a valid file path`) + } + return sources.CSV(*locFilePath, *formatsFilePath) + } + return nil } diff --git a/platforms/android.go b/platforms/android.go index 9f5ff84..8f18a3d 100644 --- a/platforms/android.go +++ b/platforms/android.go @@ -6,11 +6,11 @@ import ( "strings" "github.com/s0nerik/goloc/goloc" - "github.com/s0nerik/goloc/platforms/registry" + "github.com/s0nerik/goloc/registry" ) func init() { - registry.Register(&android{}) + registry.RegisterPlatform(&android{}) } type android struct{} diff --git a/platforms/flutter.go b/platforms/flutter.go index d27beff..1a8dc87 100644 --- a/platforms/flutter.go +++ b/platforms/flutter.go @@ -4,14 +4,14 @@ import ( "fmt" "github.com/s0nerik/goloc/goloc" "github.com/s0nerik/goloc/goloc/re" - "github.com/s0nerik/goloc/platforms/registry" + "github.com/s0nerik/goloc/registry" "io/ioutil" "path/filepath" "strings" ) func init() { - registry.Register(&flutter{}) + registry.RegisterPlatform(&flutter{}) } type flutter struct{} @@ -103,9 +103,11 @@ abstract class AppLocalizations { %s} class AppLocalizationsDelegate extends LocalizationsDelegate { + static const supportedLanguages = %s; + @override bool isSupported(Locale locale) { - return %s.contains(locale.languageCode); + return supportedLanguages.contains(locale.languageCode); } @override diff --git a/platforms/ios.go b/platforms/ios.go index b8f5594..25fbcae 100644 --- a/platforms/ios.go +++ b/platforms/ios.go @@ -7,11 +7,11 @@ import ( "strings" "github.com/s0nerik/goloc/goloc" - "github.com/s0nerik/goloc/platforms/registry" + "github.com/s0nerik/goloc/registry" ) func init() { - registry.Register(&ios{}) + registry.RegisterPlatform(&ios{}) } type ios struct{} diff --git a/platforms/json.go b/platforms/json.go index b26a5a3..552175a 100644 --- a/platforms/json.go +++ b/platforms/json.go @@ -5,11 +5,11 @@ import ( "path/filepath" "github.com/s0nerik/goloc/goloc" - "github.com/s0nerik/goloc/platforms/registry" + "github.com/s0nerik/goloc/registry" ) func init() { - registry.Register(&json{}) + registry.RegisterPlatform(&json{}) } type json struct{} diff --git a/platforms/registry/registry.go b/platforms/registry/registry.go deleted file mode 100644 index 3aa7820..0000000 --- a/platforms/registry/registry.go +++ /dev/null @@ -1,17 +0,0 @@ -package registry - -import ( - "github.com/s0nerik/goloc/goloc" -) - -var platforms []goloc.Platform - -// Register adds a new platform into the registry. -func Register(p goloc.Platform) { - platforms = append(platforms, p) -} - -// Platforms returns all platforms registered in the registry. -func Platforms() []goloc.Platform { - return platforms -} diff --git a/platforms/resolver/resolver.go b/platforms/resolver/resolver.go deleted file mode 100644 index 21f3da4..0000000 --- a/platforms/resolver/resolver.go +++ /dev/null @@ -1,22 +0,0 @@ -package resolver - -import ( - "fmt" - - "github.com/s0nerik/goloc/goloc" - "github.com/s0nerik/goloc/platforms/registry" - // It's needed to register all available platforms in the registry before running an app. - _ "github.com/s0nerik/goloc/platforms" -) - -// FindPlatform looks up and, if succeeds, returns a Platform given its name. -func FindPlatform(name string) (goloc.Platform, error) { - for _, p := range registry.Platforms() { - for _, n := range p.Names() { - if n == name { - return p, nil - } - } - } - return nil, fmt.Errorf(`platform "%v" not found`, name) -} diff --git a/registry/registry.go b/registry/registry.go new file mode 100644 index 0000000..002912b --- /dev/null +++ b/registry/registry.go @@ -0,0 +1,22 @@ +package registry + +import ( + "github.com/s0nerik/goloc/goloc" +) + +var platforms []goloc.Platform + +func RegisterPlatform(p goloc.Platform) { + platforms = append(platforms, p) +} + +func GetPlatform(name string) goloc.Platform { + for _, p := range platforms { + for _, n := range p.Names() { + if n == name { + return p + } + } + } + return nil +} diff --git a/sources/csv.go b/sources/csv.go new file mode 100644 index 0000000..6e96f85 --- /dev/null +++ b/sources/csv.go @@ -0,0 +1,45 @@ +package sources + +import ( + "encoding/csv" + "github.com/s0nerik/goloc/goloc" + "os" +) + +type csvSource struct { + localizationsFilePath string + formatsFilePath string +} + +func CSV(localizationsFilePath string, formatsFilePath string) *csvSource { + return &csvSource{ + localizationsFilePath: localizationsFilePath, + formatsFilePath: formatsFilePath, + } +} + +func readCsv(filePath string) (result [][]string, err error) { + file, err := os.Open(filePath) + if err != nil { + return result, err + } + defer file.Close() + reader := csv.NewReader(file) + return reader.ReadAll() +} + +func (s csvSource) FormatsDocumentName() string { + return s.formatsFilePath +} + +func (s csvSource) LocalizationsDocumentName() string { + return s.localizationsFilePath +} + +func (s csvSource) Formats() ([][]goloc.RawCell, error) { + return readCsv(s.formatsFilePath) +} + +func (s csvSource) Localizations() ([][]goloc.RawCell, error) { + return readCsv(s.localizationsFilePath) +} diff --git a/sources/google_sheets.go b/sources/google_sheets.go new file mode 100644 index 0000000..e97c6db --- /dev/null +++ b/sources/google_sheets.go @@ -0,0 +1,89 @@ +package sources + +import ( + "github.com/s0nerik/goloc/goloc" + "golang.org/x/net/context" + "golang.org/x/oauth2/google" + "google.golang.org/api/sheets/v4" + "io/ioutil" + "log" +) + +type googleSheets struct { + sheetID string + formatsTab string + localizationsTab string + + sheetsAPI *sheets.SpreadsheetsService +} + +func GoogleSheets( + credFilePath string, + sheetID string, + formatsTab string, + localizationsTab string, +) *googleSheets { + return &googleSheets{ + sheetID: sheetID, + formatsTab: formatsTab, + localizationsTab: localizationsTab, + sheetsAPI: sheetsAPI(credFilePath), + } +} + +func sheetsAPI(credFilePath string) *sheets.SpreadsheetsService { + ctx := context.Background() + + sec, err := ioutil.ReadFile(credFilePath) + if err != nil { + log.Fatalf("Unable to read client secret file: %v", err) + } + + config, err := google.JWTConfigFromJSON(sec, "https://www.googleapis.com/auth/spreadsheets.readonly") + if err != nil { + log.Fatalf("Unable to parse client secret file to config: %v", err) + } + + s, err := sheets.New(config.Client(ctx)) + if err != nil { + log.Fatalf("Unable to retrieve Sheets Client %v", err) + } + + return s.Spreadsheets +} + +func fetchRawValues(api *sheets.SpreadsheetsService, sheetID string, tab string) ([][]interface{}, error) { + resp, err := api.Values.Get(sheetID, tab).Do() + if err != nil { + return nil, err + } + return resp.Values, nil +} + +func fetchRawStringValues(api *sheets.SpreadsheetsService, sheetID string, tab string) ([][]string, error) { + values, err := fetchRawValues(api, sheetID, tab) + result := make([][]string, len(values)) + for i, row := range values { + result[i] = make([]string, len(row)) + for j, col := range row { + result[i][j] = col.(string) + } + } + return result, err +} + +func (s googleSheets) FormatsDocumentName() string { + return s.formatsTab +} + +func (s googleSheets) LocalizationsDocumentName() string { + return s.localizationsTab +} + +func (s googleSheets) Formats() ([][]goloc.RawCell, error) { + return fetchRawStringValues(s.sheetsAPI, s.sheetID, s.formatsTab) +} + +func (s googleSheets) Localizations() ([][]goloc.RawCell, error) { + return fetchRawStringValues(s.sheetsAPI, s.sheetID, s.localizationsTab) +}