Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module rminder

go 1.22.0
go 1.24.0

toolchain go1.24.2

require (
github.com/coreos/go-oidc/v3 v3.12.0
Expand Down Expand Up @@ -38,6 +40,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
Expand All @@ -47,7 +50,7 @@ require (
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
Expand Down Expand Up @@ -116,14 +118,18 @@ golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
9 changes: 9 additions & 0 deletions internal/app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"rminder/internal/pkg/logger"

"github.com/gin-gonic/gin"
"github.com/nicksnyder/go-i18n/v2/i18n"
)

func SetUserDatabase(ctx *gin.Context, db database.Service) {
Expand Down Expand Up @@ -43,3 +44,11 @@ func GetCSRFToken(ctx *gin.Context) string {
func GetRequestID(ctx *gin.Context) string {
return ctx.GetString("request_id")
}

func GetLocalizer(ctx *gin.Context) *i18n.Localizer {
val, exists := ctx.Get("localizer")
if !exists {
return nil
}
return val.(*i18n.Localizer)
}
2 changes: 1 addition & 1 deletion internal/app/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func LandingPageLoadHandler(s *App) gin.HandlerFunc {

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")

err := web.Render(ctx.Writer, "home-page", map[string]any{
err := web.Render(ctx, "home-page", map[string]any{
"UserExists": userExists,
})
if err != nil {
Expand Down
33 changes: 33 additions & 0 deletions internal/handlers/language/language.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package language

import (
"net/http"
i18npkg "rminder/internal/i18n"

"github.com/gin-gonic/gin"
)

func Set(ctx *gin.Context) {
lang := ctx.Param("lang")

supported := false
for _, l := range i18npkg.SupportedLanguages {
if l.Code == lang {
supported = true
break
}
}

if !supported {
ctx.Status(http.StatusBadRequest)
return
}

ctx.SetCookie("lang", lang, 365*24*3600, "/", "", false, false)

referer := ctx.GetHeader("Referer")
if referer == "" {
referer = "/"
}
ctx.Redirect(http.StatusFound, referer)
}
14 changes: 7 additions & 7 deletions internal/handlers/tasks/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func GetLists(ctx *gin.Context) {

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")

err = web.Render(ctx.Writer, "sidebar-lists", map[string]any{
err = web.Render(ctx, "sidebar-lists", map[string]any{
"Lists": lists,
"Persistence": persistence,
})
Expand Down Expand Up @@ -97,13 +97,13 @@ func GetList(ctx *gin.Context) {
ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")

if list.FilterBy == "" {
err = web.Render(ctx.Writer, "lists-content", map[string]any{
err = web.Render(ctx, "lists-content", map[string]any{
"List": list,
"Persistence": persistence,
"IsMultilist": false,
})
} else {
err = web.Render(ctx.Writer, "multi-list-content", map[string]any{
err = web.Render(ctx, "multi-list-content", map[string]any{
"Lists": lists,
"Title": list.Name,
"Persistence": persistence,
Expand Down Expand Up @@ -182,7 +182,7 @@ func CreateList(ctx *gin.Context) {
}

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
err = web.Render(ctx.Writer, "sidebar-lists", map[string]any{
err = web.Render(ctx, "sidebar-lists", map[string]any{
"Lists": lists,
"Persistence": persistence,
})
Expand Down Expand Up @@ -241,7 +241,7 @@ func DeleteList(ctx *gin.Context) {
}

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
err = web.Render(ctx.Writer, "sidebar-lists", map[string]any{
err = web.Render(ctx, "sidebar-lists", map[string]any{
"Lists": lists,
"Persistence": persistence,
})
Expand Down Expand Up @@ -316,7 +316,7 @@ func UpdateList(ctx *gin.Context) {
}

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
err = web.Render(ctx.Writer, "sidebar-lists", map[string]any{
err = web.Render(ctx, "sidebar-lists", map[string]any{
"Lists": lists,
"Persistence": persistence,
})
Expand Down Expand Up @@ -373,7 +373,7 @@ func SearchLists(ctx *gin.Context) {

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")

err = web.Render(ctx.Writer, "multi-list-content", map[string]any{
err = web.Render(ctx, "multi-list-content", map[string]any{
"Lists": lists,
"Title": "Search Results",
"Persistence": persistence,
Expand Down
18 changes: 10 additions & 8 deletions internal/handlers/tasks/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func Load(ctx *gin.Context) {

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")

err = web.Render(ctx.Writer, "tasks-page", map[string]any{
err = web.Render(ctx, "tasks-page", map[string]any{
"Lists": lists,
"MultiList": multiList,
"Persistence": persistence,
Expand Down Expand Up @@ -98,7 +98,7 @@ func GetTasks(ctx *gin.Context) {

if slug == "" {
ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
err = web.Render(ctx.Writer, "tasks-page", map[string]any{
err = web.Render(ctx, "tasks-page", map[string]any{
"Lists": lists,
"MultiList": ([]*database.List)(nil),
"Persistence": persistence,
Expand All @@ -109,7 +109,7 @@ func GetTasks(ctx *gin.Context) {
}
} else {
ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
err = web.Render(ctx.Writer, "task-list", map[string]any{
err = web.Render(ctx, "task-list", map[string]any{
"Tasks": tasks,
"SelectedTask": persistence.TaskId,
})
Expand Down Expand Up @@ -153,7 +153,9 @@ func GetTask(ctx *gin.Context) {
}

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
err = web.Render(ctx.Writer, "details", task)
err = web.Render(ctx, "details", map[string]any{
"Task": task,
})
if err != nil {
log.Error("error rendering in TaskList", "error", err)
app.ErrorInternalHTML(ctx, "Failed to render task details.")
Expand Down Expand Up @@ -207,7 +209,7 @@ func CreateTask(ctx *gin.Context) {
}

ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
err = web.Render(ctx.Writer, "task-list", map[string]any{
err = web.Render(ctx, "task-list", map[string]any{
"Tasks": tasks,
"SelectedTask": persistence.TaskId,
})
Expand Down Expand Up @@ -320,11 +322,11 @@ func UpdateTask(ctx *gin.Context) {
ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")

if slug == "important" {
err = web.Render(ctx.Writer, "task-important-elem", task)
err = web.Render(ctx, "task-important-elem", map[string]any{"Task": task})
} else if slug == "completed" {
err = web.Render(ctx.Writer, "task-completed-elem", task)
err = web.Render(ctx, "task-completed-elem", map[string]any{"Task": task})
} else {
err = web.Render(ctx.Writer, "task", map[string]any{
err = web.Render(ctx, "task", map[string]any{
"Task": task,
"SelectedTask": persistence.TaskId,
})
Expand Down
42 changes: 42 additions & 0 deletions internal/i18n/i18n.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package i18n

import (
"embed"
"encoding/json"

"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)

//go:embed locales/*.json
var localeFS embed.FS

var Bundle *i18n.Bundle

type Language struct {
Code string
Name string
}

var SupportedLanguages = []Language{
{Code: "en", Name: "English"},
{Code: "pt", Name: "Português"},
}

func init() {
Bundle = i18n.NewBundle(language.English)
Bundle.RegisterUnmarshalFunc("json", json.Unmarshal)

entries, err := localeFS.ReadDir("locales")
if err != nil {
panic("i18n: failed to read locales directory: " + err.Error())
}

for _, entry := range entries {
data, err := localeFS.ReadFile("locales/" + entry.Name())
if err != nil {
panic("i18n: failed to read locale file " + entry.Name() + ": " + err.Error())
}
Bundle.MustParseMessageFileBytes(data, entry.Name())
}
}
56 changes: 56 additions & 0 deletions internal/i18n/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"add_task": "Add a task",
"add": "Add",
"rename": "Rename",
"priority": "Priority",
"start_date": "Start Date",
"due_date": "Due Date",
"to": "to",
"start": "Start",
"due": "Due",
"set_as_complete": "Set it as complete",
"set_as_important": "Set it as important",
"delete_task_warning": "Task will be permanently deleted, you won't be able to undo this action.",
"cancel": "Cancel",
"delete_task": "Delete task",
"toggle_sidebar": "Toggle sidebar",
"search": "Search",
"search_results": "Search Results",
"my_lists": "My lists",
"add_list": "Add list",
"pin": "Pin",
"filters": "Filters",
"include": "Include",
"no_filter": "No Filter",
"any": "Any",
"all": "All",
"completed": "Completed",
"yes": "Yes",
"no": "No",
"important": "Important",
"date": "Date",
"no_date": "No Date",
"today": "Today",
"with_date": "With Date",
"on_date": "On Date",
"before_date": "Before a Date",
"after_date": "After a Date",
"on_range": "On Range",
"start_range": "Start Range",
"end_range": "End Range",
"delete": "Delete",
"home": "Home",
"tasks": "Tasks",
"import": "Import",
"export": "Export",
"select_data_file": "Select data file",
"upload": "Upload",
"next_prev_task": "Next/Previous Task",
"next_prev_list": "Next/Previous List",
"new_task": "New Task",
"new_list": "New List",
"toggle_task_completion": "Toggle Task Completion",
"toggle_task_importance": "Toggle Task Importance",
"switch_task_priority": "Switch Task Priority",
"no_tasks_found": "No Tasks Were Found"
}
56 changes: 56 additions & 0 deletions internal/i18n/locales/pt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"add_task": "Adicionar tarefa",
"add": "Adicionar",
"rename": "Renomear",
"priority": "Prioridade",
"start_date": "Data de início",
"due_date": "Data de prazo",
"to": "até",
"start": "Início",
"due": "Prazo",
"set_as_complete": "Marcar como concluída",
"set_as_important": "Marcar como importante",
"delete_task_warning": "A tarefa será excluída permanentemente, não será possível desfazer esta ação.",
"cancel": "Cancelar",
"delete_task": "Excluir tarefa",
"toggle_sidebar": "Alternar barra lateral",
"search": "Pesquisar",
"search_results": "Resultados da pesquisa",
"my_lists": "Minhas listas",
"add_list": "Adicionar lista",
"pin": "Fixar",
"filters": "Filtros",
"include": "Incluir",
"no_filter": "Sem filtro",
"any": "Qualquer",
"all": "Todos",
"completed": "Concluída",
"yes": "Sim",
"no": "Não",
"important": "Importante",
"date": "Data",
"no_date": "Sem data",
"today": "Hoje",
"with_date": "Com data",
"on_date": "Em data",
"before_date": "Antes de uma data",
"after_date": "Após uma data",
"on_range": "Em intervalo",
"start_range": "Início do intervalo",
"end_range": "Fim do intervalo",
"delete": "Excluir",
"home": "Início",
"tasks": "Tarefas",
"import": "Importar",
"export": "Exportar",
"select_data_file": "Selecionar arquivo de dados",
"upload": "Enviar",
"next_prev_task": "Próxima/Anterior Tarefa",
"next_prev_list": "Próxima/Anterior Lista",
"new_task": "Nova Tarefa",
"new_list": "Nova Lista",
"toggle_task_completion": "Alternar Conclusão da Tarefa",
"toggle_task_importance": "Alternar Importância da Tarefa",
"switch_task_priority": "Mudar Prioridade da Tarefa",
"no_tasks_found": "Nenhuma Tarefa Encontrada"
}
Loading