make crowdin json (#87)

This commit is contained in:
Tyr Mactire 2022-08-16 16:08:42 -07:00 committed by GitHub
parent 537158558d
commit d873b9eaea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 386 additions and 167 deletions

View File

@ -31,13 +31,13 @@ fmt:
@go fmt $(shell go list ./... | grep -v /vendor/)
i18n-extract:
goi18n extract -format toml -outdir web/locales
goi18n extract -format yaml -outdir web/locales -crowdin
i18n-merge:
goi18n merge -format toml -outdir locales web/locales/active.*.toml web/locales/translate.*.toml
goi18n merge -format yaml -outdir locales web/locales/active.*.toml web/locales/translate.*.toml -crowdin
i18n-translations:
goi18n merge -format toml -outdir locales web/locales/active.*.toml
goi18n merge -format yaml -outdir locales web/locales/active.*.toml -crowdin
lint:
@echo linting

View File

@ -1,4 +1,4 @@
---
files:
- source: /web/locales/active.en.toml
translation: /web/locales/active.%two_letters_code%.toml
- source: /web/locales/active.en.yaml
translation: /web/locales/active.%two_letters_code%.yaml

6
go.mod
View File

@ -21,7 +21,6 @@ require (
github.com/jackc/pgx/v4 v4.17.0
github.com/nickname76/telegrambot v1.2.1
github.com/nicksnyder/go-i18n/v2 v2.2.0
github.com/pelletier/go-toml/v2 v2.0.1
github.com/rbcervilla/redisstore/v8 v8.1.0
github.com/sirupsen/logrus v1.9.0
github.com/speps/go-hashids/v2 v2.0.1
@ -42,6 +41,7 @@ require (
golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
golang.org/x/text v0.3.7
gopkg.in/yaml.v2 v2.4.0
modernc.org/sqlite v1.17.1
)
@ -84,6 +84,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nickname76/repeater v1.0.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
@ -116,7 +117,6 @@ require (
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.36.0 // indirect
@ -128,3 +128,5 @@ require (
modernc.org/strutil v1.1.1 // indirect
modernc.org/token v1.0.0 // indirect
)
replace github.com/nicksnyder/go-i18n/v2 => github.com/tyrm/go-i18n/v2 v2.0.0-20220816225026-eff1682f19d0

4
go.sum
View File

@ -359,8 +359,6 @@ github.com/nickname76/repeater v1.0.1 h1:RLACAbPHMwn37XvwwwTtqhZMa/M1nwsodxQ5rmj
github.com/nickname76/repeater v1.0.1/go.mod h1:oJnqHAjbEamcjVBm8sHaexIVFdicS2YU6cJK0KZhgig=
github.com/nickname76/telegrambot v1.2.1 h1:9JaeUVkkJ9/C9WJ93pGbeOV5zsPQ8XIO8coJQ80FxM0=
github.com/nickname76/telegrambot v1.2.1/go.mod h1:0m8zn4r0Hb1+GmBEj9SoxQBhG1tg+pBEaKZ9agMeFDQ=
github.com/nicksnyder/go-i18n/v2 v2.2.0 h1:MNXbyPvd141JJqlU6gJKrczThxJy+kdCNivxZpBQFkw=
github.com/nicksnyder/go-i18n/v2 v2.2.0/go.mod h1:4OtLfzqyAxsscyCb//3gfqSvBc81gImX91LrZzczN1o=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@ -453,6 +451,8 @@ github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYm
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/tyrm/go-i18n/v2 v2.0.0-20220816225026-eff1682f19d0 h1:yE5gKFaBuudL48bQAyCRwozYnqwlWjNC7LArYvWB7xM=
github.com/tyrm/go-i18n/v2 v2.0.0-20220816225026-eff1682f19d0/go.mod h1:4OtLfzqyAxsscyCb//3gfqSvBc81gImX91LrZzczN1o=
github.com/uptrace/bun v1.1.7 h1:biOoh5dov69hQPBlaRsXSHoEOIEnCxFzQvUmbscSNJI=
github.com/uptrace/bun v1.1.7/go.mod h1:Z2Pd3cRvNKbrYuL6Gp1XGjA9QEYz+rDz5KkEi9MZLnQ=
github.com/uptrace/bun/dialect/pgdialect v1.1.7 h1:94GPc8RRC9AVoQ+4KCqRX2zScevsVfOttk13wm60/P8=

View File

@ -3,8 +3,8 @@ package language
import (
"github.com/feditools/relay/web"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/pelletier/go-toml/v2"
"golang.org/x/text/language"
"gopkg.in/yaml.v2"
"io/ioutil"
"strings"
)
@ -27,14 +27,14 @@ func New() (*Module, error) {
langBundle: i18n.NewBundle(DefaultLanguage),
}
module.langBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
module.langBundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
dir, err := web.Files.ReadDir("locales")
if err != nil {
return nil, err
}
for _, d := range dir {
if d.IsDir() || !strings.HasSuffix(d.Name(), ".toml") {
if d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
continue
}
l.Debugf("loading language file: %s", d.Name())
@ -51,7 +51,11 @@ func New() (*Module, error) {
return nil, err
}
module.langBundle.MustParseMessageFileBytes(buffer, d.Name())
// paris if not empty
empty := isEmptyYaml(buffer)
if !empty {
module.langBundle.MustParseMessageCrowdinFileBytes(buffer, d.Name())
}
}
return &module, nil
@ -59,3 +63,16 @@ func New() (*Module, error) {
// Language returns the default language.
func (m Module) Language() language.Tag { return m.lang }
func isEmptyYaml(b []byte) bool {
switch string(b) {
case "":
return true
case "---":
return true
case "---\n":
return true
default:
return false
}
}

View File

@ -1,6 +1,7 @@
package language
import (
"fmt"
"testing"
"golang.org/x/text/language"
@ -40,6 +41,35 @@ func TestNew(t *testing.T) {
}
}
func TestIsEmptyYaml(t *testing.T) {
t.Parallel()
tables := []struct {
input string
output bool
}{
{"", true},
{"---", true},
{"---\n", true},
{"---\nvalid: yaml", false},
}
for i, table := range tables {
i := i
table := table
name := fmt.Sprintf("[%d] running isEmptyYaml", i)
t.Run(name, func(t *testing.T) {
t.Parallel()
result := isEmptyYaml([]byte(table.input))
if result != table.output {
t.Errorf("[%d] got invalid response, got: %v, want: %v,", i, result, table.output)
}
})
}
}
// text testers
type testTextTable struct {

View File

@ -73,7 +73,24 @@ func (b *Bundle) MustLoadMessageFile(path string) {
//
// The language tag of the file is everything after the second to last "." or after the last path separator, but before the format.
func (b *Bundle) ParseMessageFileBytes(buf []byte, path string) (*MessageFile, error) {
messageFile, err := ParseMessageFileBytes(buf, path, b.unmarshalFuncs)
messageFile, err := ParseMessageFileBytes(buf, path, b.unmarshalFuncs, false)
if err != nil {
return nil, err
}
if err := b.AddMessages(messageFile.Tag, messageFile.Messages...); err != nil {
return nil, err
}
return messageFile, nil
}
// ParseMessageCrowdinFileBytes parses the bytes in buf to add translations to the bundle in the crowdin format.
//
// The format of the file is everything after the last ".".
//
// The language tag of the file is everything after the second to last "." or after the last path separator, but before the format.
func (b *Bundle) ParseMessageCrowdinFileBytes(buf []byte, path string) (*MessageFile, error) {
messageFile, err := ParseMessageFileBytes(buf, path, b.unmarshalFuncs, true)
if err != nil {
return nil, err
}
@ -91,6 +108,14 @@ func (b *Bundle) MustParseMessageFileBytes(buf []byte, path string) {
}
}
// MustParseMessageCrowdinFileBytes is similar to ParseMessageCrowdinFileBytes
// except it panics if an error happens.
func (b *Bundle) MustParseMessageCrowdinFileBytes(buf []byte, path string) {
if _, err := b.ParseMessageCrowdinFileBytes(buf, path); err != nil {
panic(err)
}
}
// AddMessages adds messages for a language.
// It is useful if your messages are in a format not supported by ParseMessageFileBytes.
func (b *Bundle) AddMessages(tag language.Tag, messages ...*Message) error {

View File

@ -9,6 +9,15 @@ import (
)
// Localizer provides Localize and MustLocalize methods that return localized messages.
// Localize and MustLocalize methods use a language.Tag matching algorithm based
// on the best possible value. This algorithm may cause an unexpected language.Tag returned
// value depending on the order of the tags stored in memory. For example, if the bundle
// used to create a Localizer instance ingested locales following this order
// ["en-US", "en-GB", "en-IE", "en"] and the locale "en" is asked, the underlying matching
// algorithm will return "en-US" thinking it is the best match possible. More information
// about the algorithm in this Github issue: https://github.com/golang/go/issues/49176.
// There is additionnal informations inside the Go code base:
// https://github.com/golang/text/blob/master/language/match.go#L142
type Localizer struct {
// bundle contains the messages that can be returned by the Localizer.
bundle *Bundle

View File

@ -18,7 +18,7 @@ type MessageFile struct {
}
// ParseMessageFileBytes returns the messages parsed from file.
func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]UnmarshalFunc) (*MessageFile, error) {
func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]UnmarshalFunc, crowdin bool) (*MessageFile, error) {
lang, format := parsePath(path)
tag := language.Make(lang)
messageFile := &MessageFile{
@ -43,6 +43,25 @@ func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]Un
return nil, err
}
if crowdin {
switch data := raw.(type) {
case map[string]interface{}:
rawSub, ok := data[tag.String()]
if !ok {
return nil, fmt.Errorf("file is not in crowdin format: %T is missing language key", raw)
}
raw = rawSub
case map[interface{}]interface{}:
rawSub, ok := data[tag.String()]
if !ok {
return nil, fmt.Errorf("file is not in crowdin format: %T is missing language key", raw)
}
raw = rawSub
default:
return nil, fmt.Errorf("file is not in crowdin format: unknown type %T", raw)
}
}
if messageFile.Messages, err = recGetMessages(raw, isMessage(raw), true); err != nil {
return nil, err
}

3
vendor/modules.txt vendored
View File

@ -378,7 +378,7 @@ github.com/nickname76/repeater
# github.com/nickname76/telegrambot v1.2.1
## explicit; go 1.18
github.com/nickname76/telegrambot
# github.com/nicksnyder/go-i18n/v2 v2.2.0
# github.com/nicksnyder/go-i18n/v2 v2.2.0 => github.com/tyrm/go-i18n/v2 v2.0.0-20220816225026-eff1682f19d0
## explicit; go 1.12
github.com/nicksnyder/go-i18n/v2/i18n
github.com/nicksnyder/go-i18n/v2/internal
@ -820,3 +820,4 @@ modernc.org/strutil
# modernc.org/token v1.0.0
## explicit
modernc.org/token
# github.com/nicksnyder/go-i18n/v2 => github.com/tyrm/go-i18n/v2 v2.0.0-20220816225026-eff1682f19d0

View File

View File

View File

View File

View File

View File

View File

View File

@ -1,150 +0,0 @@
Admin = "Admin"
AllowSearchEngineIndexing = "Allow Search Engine Indexing"
BlockExistsDomain = "Block for domain {{.Domain}} already exists."
Blocked = "Blocked"
Close = "Close"
Create = "Create"
Delete = "Delete"
DeleteBlockConfirmDomain = "Are you sure you want to delete the block for {{.Domain}}?"
DeleteBlockDomain = "Delete Block {{.Domain}}"
EditBlockDomain = "Edit Block {{.Domain}}"
Enabled = "Enabled"
ErrorDatabase = "database error"
Federation = "Federation"
Following = "Following"
General = "General"
HomePageBody = "Home Page Body"
HomeWeb = "Home"
HowToJoin = "How to Join"
HowToJoinActor = "For Pleroma and other instances, you may subscribe to this relay with the address"
HowToJoinInbox = "For Mastodon and Misskey instances, you may subscribe to this relay with the address"
Import = "Import"
Linear = "Linear"
Logarithmic = "Logarithmic"
Login = "Login"
Logout = "Logout"
LooksGood = "Looks Good!"
OAuthConfigured = "OAuth Configured"
Save = "Save"
Software = "Software"
Unauthorized = "Unauthorized"
[Account]
one = "Account"
other = "Accounts"
[Activity]
one = "Activity"
other = "Activities"
[ActivityLog]
one = "Activity Log"
other = "Activity Logs"
[ActorURI]
one = "Actor URI"
other = "Actor URIs"
[AddBlock]
one = "Add Block"
other = "Add Blocks"
[Block]
one = "Block"
other = "Blocks"
[BlockSubdomain]
one = "Block Subdomain"
other = "Block Subdomains"
[BlockedDomain]
one = "Blocked Domain"
other = "Blocked Domains"
[ChatID]
one = "Chat ID"
other = "Chat IDs"
[Config]
one = "Config"
other = "Configs"
[DayCount]
one = "{{.Count}} Day"
other = "{{.Count}} Days"
[DeliveredActivity]
one = "Delivered Activity"
other = "Delivered Activities"
[Description]
one = "Description"
other = "Descriptions"
[Domain]
one = "Domain"
other = "Domains"
[ImportBlockList]
one = "Import Block List"
other = "Import Block Lists"
[InboxURI]
one = "Inbox URI"
other = "Inbox URIs"
[Instance]
one = "Instance"
other = "Instances"
[Metric]
one = "Metric"
other = "Metrics"
[Moderator]
one = "Moderator"
other = "Moderators"
[Notification]
one = "Notification"
other = "Notifications"
[ObfuscatedDomain]
one = "Obfuscated Domain"
other = "Obfuscated Domains"
[ReceivedActivity]
one = "Received Activity"
other = "Received Activities"
[Relay]
one = "Relay"
other = "Relays"
[Repo]
one = "Repo"
other = "Repos"
[ServerHostname]
one = "Server Hostname"
other = "Server Hostnames"
[Setting]
one = "Setting"
other = "Settings"
[Statistic]
one = "Statistic"
other = "Statistics"
[Timestamp]
one = "Timestamp"
other = "Timestamps"
[Token]
one = "Token"
other = "Tokens"
[Update]
one = "Update"
other = "Updates"

121
web/locales/active.en.yaml Normal file
View File

@ -0,0 +1,121 @@
en:
Account:
one: Account
other: Accounts
Activity:
one: Activity
other: Activities
ActivityLog:
one: Activity Log
other: Activity Logs
ActorURI:
one: Actor URI
other: Actor URIs
AddBlock:
one: Add Block
other: Add Blocks
Admin: Admin
AllowSearchEngineIndexing: Allow Search Engine Indexing
Block:
one: Block
other: Blocks
BlockExistsDomain: Block for domain {{.Domain}} already exists.
BlockSubdomain:
one: Block Subdomain
other: Block Subdomains
Blocked: Blocked
BlockedDomain:
one: Blocked Domain
other: Blocked Domains
ChatID:
one: Chat ID
other: Chat IDs
Close: Close
Config:
one: Config
other: Configs
Create: Create
DayCount:
one: '{{.Count}} Day'
other: '{{.Count}} Days'
Delete: Delete
DeleteBlockConfirmDomain: Are you sure you want to delete the block for {{.Domain}}?
DeleteBlockDomain: Delete Block {{.Domain}}
DeliveredActivity:
one: Delivered Activity
other: Delivered Activities
Description:
one: Description
other: Descriptions
Domain:
one: Domain
other: Domains
EditBlockDomain: Edit Block {{.Domain}}
Enabled: Enabled
ErrorDatabase: database error
Federation: Federation
Following: Following
General: General
HomePageBody: Home Page Body
HomeWeb: Home
HowToJoin: How to Join
HowToJoinActor: For Pleroma and other instances, you may subscribe to this relay with the address
HowToJoinInbox: For Mastodon and Misskey instances, you may subscribe to this relay with the address
Import: Import
ImportBlockList:
one: Import Block List
other: Import Block Lists
InboxURI:
one: Inbox URI
other: Inbox URIs
Instance:
one: Instance
other: Instances
Linear: Linear
Logarithmic: Logarithmic
Login: Login
Logout: Logout
LooksGood: Looks Good!
Metric:
one: Metric
other: Metrics
Moderator:
one: Moderator
other: Moderators
Notification:
one: Notification
other: Notifications
OAuthConfigured: OAuth Configured
ObfuscatedDomain:
one: Obfuscated Domain
other: Obfuscated Domains
ReceivedActivity:
one: Received Activity
other: Received Activities
Relay:
one: Relay
other: Relays
Repo:
one: Repo
other: Repos
Save: Save
ServerHostname:
one: Server Hostname
other: Server Hostnames
Setting:
one: Setting
other: Settings
Software: Software
Statistic:
one: Statistic
other: Statistics
Timestamp:
one: Timestamp
other: Timestamps
Token:
one: Token
other: Tokens
Unauthorized: Unauthorized
Update:
one: Update
other: Updates

118
web/locales/active.es.yaml Normal file
View File

@ -0,0 +1,118 @@
es:
Account:
one: Cuenta
other: Cuentas
Activity:
one: Actividad
other: Actividades
ActivityLog:
one: Registro de Actividades
other: Registros de Actividad
ActorURI:
one: URI del Actor
other: URIs del Actor
AddBlock:
one: Añadir Bloque
other: Añadir Bloques
Admin: Admin
AllowSearchEngineIndexing: Permitir la Indexación de Motores de Búsqueda
Block:
one: Bloque
other: Bloques
BlockExistsDomain: El bloqueo para el dominio {{.Domain}} ya existe.
BlockSubdomain:
one: Bloquear Subdominio
other: Bloquear Subdominios
Blocked: Bloqueado
BlockedDomain:
one: Dominio Bloqueado
other: Dominios Boqueados
ChatID:
one: ID del Chat
other: Identificadores de Chat
Close: Cerrar
Config:
one: Config
other: Configs
Create: Crear
Delete: Borrar
DeleteBlockConfirmDomain: '¿Está seguro de que desea eliminar el bloqueo de {{.Domain}}?'
DeleteBlockDomain: Borrar Bloque {{.Domain}}
DeliveredActivity:
one: Actividad Entregada
other: Actividades Entregadas
Description:
one: Descripción
other: Descripciones
Domain:
one: Dominio
other: Dominios
EditBlockDomain: Editar Bloque {{.Domain}}
Enabled: Activado
ErrorDatabase: error en la base de datos
Federation: Federación
Following: Siguiendo
General: General
HomePageBody: Cuerpo de la Página de Inicio
HomeWeb: Inicio
HowToJoin: Cómo Unirse
HowToJoinActor: Para Pleroma y otras instancias, puede suscribirse a este relé con la dirección
HowToJoinInbox: Para las instancias de Mastodon y Misskey, puede suscribirse a este relé con la dirección
Import: Importar
ImportBlockList:
one: Importar Lista de Bloqueo
other: Importar Listas de Bloqueo
InboxURI:
one: URI de Bandeja de Entrada
other: URIs de Bandeja de Entrada
Instance:
one: Instancia
other: Instancias
Linear: Lineal
Logarithmic: Logarítmico
Login: Iniciar Sesión
Logout: Cerrar Sesión
LooksGood: '¡Se ve bien!'
Metric:
one: Métrica
other: Métricas
Moderator:
one: Moderador
other: Moderadores
Notification:
one: Notificación
other: Notificaciones
OAuthConfigured: OAuth Configurada
ObfuscatedDomain:
one: Dominio Ofuscado
other: Dominios Ofuscados
ReceivedActivity:
one: Actividad Recibida
other: Actividades Recibidas
Relay:
one: Relé
other: Relés
Repo:
one: Repo
other: Repos
Save: Guardar
ServerHostname:
one: Nombre del Servidor
other: Nombres de Servidor
Setting:
one: Ajuste
other: Ajustes
Software: Software
Statistic:
one: Estadística
other: Estadísticas
Timestamp:
one: Marca de Tiempo
other: Marcas de Tiempo
Token:
one: Ficha
other: Fichas
Unauthorized: No Autorizado
Update:
one: Actualizar
other: Actualizaciones

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -0,0 +1,27 @@
ru:
Admin: Администратор
AllowSearchEngineIndexing: Разрешить индексирование поисковыми системами
Blocked: Заблокировано
Close: Закрыть
Create: Создать
Delete: Удалить
DeleteBlockConfirmDomain: Вы действительно хотите удалить блокировку для {{.Domain}}?
DeleteBlockDomain: Удалить блокировку {{.Domain}}
EditBlockDomain: Редактировать блокировку {{.Domain}}
Enabled: Включено
ErrorDatabase: ошибка базы данных
Federation: Федерация
Following: Подписки
General: Общие
HomeWeb: Главная
HowToJoin: Как присоединиться
HowToJoinActor: Для Pleroma и других серверов, вы можете подписатья на этот ретранслятор с помощью адреса
HowToJoinInbox: Для серверов Mastodon и Misskey, вы модете подписаться на этот ретранслятор с помощью адреса
Import: Импортировать
Linear: Линейная
Logarithmic: Логарифмическая
Login: Войти
Logout: Выйти
OAuthConfigured: OAuth настроен
Save: Сохранить
Software: Программное обеспечение

View File

View File

View File

View File

View File

View File

View File

@ -3,7 +3,7 @@ package web
import "embed"
// Files contains static files required by the application
//go:embed locales/active.*.toml
//go:embed locales/active.*.yaml
//go:embed static/css/default.min.css
//go:embed static/css/error.min.css
//go:embed static/css/login.min.css