feat(docs): various improvements
- removing spaces at end of line - fixing spelling and grammar mistakes - making sure 'Vikunja' is spelled the same way everywhere - prefer using editors word wrap instead of hardcoding word wrap in markdown (reason: different word wrap per editor & end of line space) - add newline add end where missing - remove double colon at end of headlines - remove unnecessary indention - make sure code blocks and headlines etc always have an empty line around
This commit is contained in:

committed by
konrad

parent
6cbaf5bbf9
commit
aaa0593289
@ -12,10 +12,10 @@ menu:
|
||||
|
||||
All cli-related functions are located in `pkg/cmd`.
|
||||
Each cli command usually calls a function in another package.
|
||||
For example, the `vikunja migrate` command calls `migration.Migrate()`.
|
||||
For example, the `vikunja migrate` command calls `migration.Migrate()`.
|
||||
|
||||
Vikunja uses the amazing [cobra](https://github.com/spf13/cobra) library for its cli.
|
||||
Please refer to its documentation for informations about how to use flags etc.
|
||||
Please refer to its documentation for information about how to use flags etc.
|
||||
|
||||
To add a new cli command, add something like the following:
|
||||
|
||||
|
@ -29,7 +29,7 @@ Then run `mage generate-docs` to generate the configuration docs from the sample
|
||||
|
||||
## Getting Configuration Values
|
||||
|
||||
To retreive a configured value call the key with a getter for the type you need.
|
||||
To retrieve a configured value call the key with a getter for the type you need.
|
||||
For example:
|
||||
|
||||
{{< highlight golang >}}
|
||||
|
@ -12,9 +12,7 @@ menu:
|
||||
Cron jobs are tasks which run on a predefined schedule.
|
||||
Vikunja uses these through a light wrapper package around the excellent [github.com/robfig/cron](https://github.com/robfig/cron) package.
|
||||
|
||||
The package exposes a `cron.Schedule` method with two arguments: The first one to define the schedule when the cron task
|
||||
should run, and the second one with the actual function to run at the schedule.
|
||||
You would then create a new function to register your the actual cron task in your package.
|
||||
The package exposes a `cron.Schedule` method with two arguments: The first one to define the schedule when the cron task should run, and the second one with the actual function to run at the schedule. You would then create a new function to register your the actual cron task in your package.
|
||||
|
||||
A basic function to register a cron task looks like this:
|
||||
|
||||
|
@ -26,8 +26,7 @@ To add a new table to the database, create the struct and [add a migration for i
|
||||
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/).
|
||||
|
||||
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table
|
||||
name matches the rest of the tables - plural.
|
||||
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table name matches the rest of the tables - plural.
|
||||
|
||||
## Adding data to test fixtures
|
||||
|
||||
@ -36,5 +35,4 @@ Adding data for test fixtures can be done via `yaml` files in `pkg/models/fixtur
|
||||
The name of the yaml file should match the table name in the database.
|
||||
Adding values to it is done via array definition inside it.
|
||||
|
||||
**Note**: Table and column names need to be in snake_case as that's what is used internally in the database
|
||||
and for mapping values from the database to xorm so your structs can use it.
|
||||
**Note**: Table and column names need to be in snake_case as that's what is used internally in the database and for mapping values from the database to xorm so your structs can use it.
|
@ -25,7 +25,7 @@ All migrations are stored in `pkg/migrations` and files should have the same nam
|
||||
Each migration should have a function to apply and roll it back, as well as a numeric id (the datetime)
|
||||
and a more in-depth description of what the migration actually does.
|
||||
|
||||
To easily get a new id, run the following on any unix system:
|
||||
To easily get a new id, run the following on any unix system:
|
||||
|
||||
{{< highlight bash >}}
|
||||
date +%Y%m%d%H%M%S
|
||||
@ -75,4 +75,4 @@ func init() {
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
You should always copy the changed parts of the struct you're changing when adding migraitons.
|
||||
You should always copy the changed parts of the struct you're changing when adding migrations.
|
||||
|
@ -48,7 +48,7 @@ A release gets tagged from the main branch with the version name as tag name.
|
||||
|
||||
Backports and point-releases should go to a `release/version` branch, based on the tag they are building on top of.
|
||||
|
||||
## Conventional commits
|
||||
## Conventional Commits
|
||||
|
||||
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify generating release notes.
|
||||
|
||||
|
@ -13,14 +13,14 @@ menu:
|
||||
All custom errors are defined in `pkg/models/errors.go`.
|
||||
You should add new ones in this file.
|
||||
|
||||
Custom errors usually have fields for the http return code, a [vikunja-specific error code]({{< ref "../usage/errors.md">}})
|
||||
Custom errors usually have fields for the http return code, a [Vikunja-specific error code]({{< ref "../usage/errors.md">}})
|
||||
and a human-readable error message about what went wrong.
|
||||
|
||||
An error consists of multiple functions and definitions:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// This struct holds any information about this specific error.
|
||||
// In this case, it contains the user ID of a nonexistand user.
|
||||
// In this case, it contains the user ID of a nonexistent user.
|
||||
// This type should always be a struct, even if it has no values in it.
|
||||
|
||||
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
|
||||
@ -44,21 +44,21 @@ func (err ErrUserDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("User does not exist [user id: %d]", err.UserID)
|
||||
}
|
||||
|
||||
// This const holds the vikunja error code used to be able to identify this error without having to
|
||||
// This const holds the Vikunja error code used to be able to identify this error without having to
|
||||
// rely on an error string.
|
||||
// This needs to be unique, so you should check whether the error code exists or not.
|
||||
// The general convention for error codes is as follows:
|
||||
// * Every "group" errors lives in a thousend something. For example all user issues are 1000-something, all
|
||||
// project errors are 3000-something and so on.
|
||||
// * New error codes should be the current max error code + 1. Don't take free numbers to prevent old errors
|
||||
// which are depricated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004,
|
||||
// which are deprecated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004,
|
||||
// a new error should be 1005 and not 1003.
|
||||
|
||||
// ErrCodeUserDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeUserDoesNotExist = 1005
|
||||
|
||||
// This is the implementation which returns an http error which is then passed to the client.
|
||||
// Here you define the http status code with which one the error will be returned, the vikunja error code and
|
||||
// Here you define the http status code with which one the error will be returned, the Vikunja error code and
|
||||
// a human-readable error message.
|
||||
|
||||
// HTTPError holds the http error description
|
||||
|
@ -42,7 +42,7 @@ You then get the event with all its data back in the listener, see below.
|
||||
#### Naming Convention
|
||||
|
||||
Event names should roughly have the entity they're dealing with on the left and the action on the right of the name, separated by `.`.
|
||||
There's no limit to how "deep" or specifig an event name can be.
|
||||
There's no limit to how "deep" or specific an event name can be.
|
||||
|
||||
The name should have the most general concept it's describing at the left, getting more specific on the right of it.
|
||||
|
||||
@ -104,7 +104,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
As you can see, the curent task and doer are injected into it.
|
||||
As you can see, the current task and doer are injected into it.
|
||||
|
||||
### Special Events
|
||||
|
||||
@ -133,7 +133,7 @@ type Listener interface {
|
||||
The `Handle` method is executed when the event this listener listens on is dispatched.
|
||||
* As the single parameter, it gets the payload of the event, which is the event struct when it was dispatched decoded as json object and passed as a slice of bytes.
|
||||
To use it you'll need to unmarshal it. Unfortunately there's no way to pass an already populated event object to the function because we would not know what type it has when parsing it.
|
||||
* If the handler returns an error, the listener is retried 5 times, with an exponentional back-off period in between retries.
|
||||
* If the handler returns an error, the listener is retried 5 times, with an exponential back-off period in between retries.
|
||||
If it still fails after the fifth retry, the event is nack'd and it's up to the event dispatcher to resend it.
|
||||
You can learn more about this mechanism in the [watermill documentation](https://watermill.io/docs/middlewares/#retry).
|
||||
|
||||
@ -148,7 +148,7 @@ The easiest way to create a new listener for an event is with mage:
|
||||
mage dev:make-listener <listener-name> <event-name> <package>
|
||||
```
|
||||
|
||||
This will create a new listener type in the `pkg/<package>/listners.go` file and implement the `Handle` and `Name` methods.
|
||||
This will create a new listener type in the `pkg/<package>/listeners.go` file and implement the `Handle` and `Name` methods.
|
||||
It will also pre-generate some boilerplate code to unmarshal the event from the payload.
|
||||
|
||||
Furthermore, it will register the listener for its event in the `RegisterListeners()` method of the same file.
|
||||
@ -157,7 +157,7 @@ This function is called at startup and has to contain all events you want to lis
|
||||
### Listening for Events
|
||||
|
||||
To listen for an event, you need to register the listener for the event it should be called for.
|
||||
This usually happens in the `RegisterListeners()` method in `pkg/<package>/listners.go` which is called at start up.
|
||||
This usually happens in the `RegisterListeners()` method in `pkg/<package>/listeners.go` which is called at start up.
|
||||
|
||||
The listener will never be executed if it hasn't been registered.
|
||||
|
||||
@ -179,7 +179,7 @@ func (s *IncreaseTaskCounter) Name() string {
|
||||
return "task.counter.increase"
|
||||
}
|
||||
|
||||
// Hanlde is executed when the event IncreaseTaskCounter listens on is fired
|
||||
// Handle is executed when the event IncreaseTaskCounter listens on is fired
|
||||
func (s *IncreaseTaskCounter) Handle(payload message.Payload) (err error) {
|
||||
return keyvalue.IncrBy(metrics.TaskCountKey, 1)
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ go install github.com/magefile/mage
|
||||
There are multiple categories of subcommands in the magefile:
|
||||
|
||||
* `build`: Contains commands to build a single binary
|
||||
* `check`: Contains commands to statically check the source code
|
||||
* `check`: Contains commands to statically check the source code
|
||||
* `release`: Contains commands to release Vikunja with everything that's required
|
||||
* `test`: Contains commands to run all kinds of tests
|
||||
* `dev`: Contains commands to run development tasks
|
||||
@ -114,7 +114,7 @@ binary to be able to use it.
|
||||
* `mage release:check` creates sha256 checksums for each binary which will be included in the zip file
|
||||
* `mage release:os-package` bundles a binary with the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture
|
||||
* `mage release:compress` compresses all build binaries with `upx` to save space
|
||||
* `mage release:zip` paclages a zip file for the files created by `release:os-package`
|
||||
* `mage release:zip` packages a zip file for the files created by `release:os-package`
|
||||
|
||||
### Build os packages
|
||||
|
||||
@ -168,7 +168,7 @@ Runs all integration tests.
|
||||
mage dev:create-migration
|
||||
{{< /highlight >}}
|
||||
|
||||
Creates a new migration with the current date.
|
||||
Creates a new migration with the current date.
|
||||
Will ask for the name of the struct you want to create a migration for.
|
||||
|
||||
See also [migration docs]({{< ref "mage.md" >}}).
|
||||
|
@ -34,14 +34,13 @@ promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
})
|
||||
{{< /highlight >}}
|
||||
|
||||
Then you'll need to set the metrics initial value on every startup of vikunja.
|
||||
Then you'll need to set the metrics initial value on every startup of Vikunja.
|
||||
This is done in `pkg/routes/routes.go` to avoid cyclic imports.
|
||||
If metrics are enabled, it checks if a redis connection is available and then sets the initial values.
|
||||
A convenience function is available if the metric is based on a database struct.
|
||||
|
||||
Because metrics are stored in redis, you are responsible to increase or decrease these based on criteria you define.
|
||||
To do this, use `metrics.UpdateCount(value, key)` where `value` is the amount you want to cange it (you can pass
|
||||
negative values to decrease it) and `key` it the redis key used to define the metric.
|
||||
To do this, use `metrics.UpdateCount(value, key)` where `value` is the amount you want to change it (you can pass negative values to decrease it) and `key` it the redis key used to define the metric.
|
||||
|
||||
## Using it
|
||||
|
||||
|
@ -17,12 +17,9 @@ In general, each migrator implements a migrator interface which is then called f
|
||||
The interface makes it possible to use helper methods which handle http and focus only on the implementation of the migrator itself.
|
||||
|
||||
There are two ways of migrating data from another service:
|
||||
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an
|
||||
oauth flow. You can then call the service's api on behalf of your user to get all the data.
|
||||
The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
|
||||
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need
|
||||
to parse the file and create the projects, tasks etc.
|
||||
The Vikunja File Import uses this pattern.
|
||||
|
||||
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an oauth flow. You can then call the service's api on behalf of your user to get all the data. The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
|
||||
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need to parse the file and create the projects, tasks etc. The Vikunja File Import uses this pattern.
|
||||
|
||||
To differentiate the two, there are two different interfaces you must implement.
|
||||
|
||||
@ -43,7 +40,7 @@ type Migrator interface {
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
// Migrate is the interface used to migrate a user's tasks from another platform to vikunja.
|
||||
// Migrate is the interface used to migrate a user's tasks from another platform to Vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *models.User) error
|
||||
// AuthURL returns a url for clients to authenticate against.
|
||||
@ -61,7 +58,7 @@ type FileMigrator interface {
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
// Migrate is the interface used to migrate a user's tasks, projects and other things from a file to vikunja.
|
||||
// Migrate is the interface used to migrate a user's tasks, projects and other things from a file to Vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *user.User, file io.ReaderAt, size int64) error
|
||||
}
|
||||
@ -102,35 +99,32 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
|
||||
|
||||
## Insertion helper method
|
||||
|
||||
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
|
||||
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
|
||||
This means you start by adding a namespace, then add projects inside that namespace, then tasks in the lists and so on.
|
||||
|
||||
The root structure must be present as `[]*models.NamespaceWithProjectsAndTasks`. It allows to represent all of Vikunja's
|
||||
hierachie as a single data structure.
|
||||
The root structure must be present as `[]*models.NamespaceWithProjectsAndTasks`. It allows to represent all of Vikunja's hierarchy as a single data structure.
|
||||
|
||||
Then call the method like so:
|
||||
|
||||
```go
|
||||
fullVikunjaHierachie, err := convertWunderlistToVikunja(wContent)
|
||||
fullVikunjaHierarchy, err := convertWunderlistToVikunja(wContent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = migration.InsertFromStructure(fullVikunjaHierachie, user)
|
||||
err = migration.InsertFromStructure(fullVikunjaHierarchy, user)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
If your migrator is an oauth-based one, you should add at least an option to enable or disable it.
|
||||
Chances are, you'll need some more options for things like client ID and secret
|
||||
(if the other service uses oAuth as an authentication flow).
|
||||
Chances are, you'll need some more options for things like client ID and secret (if the other service uses oAuth as an authentication flow).
|
||||
|
||||
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when
|
||||
registering the routes, and then simply don't registering the routes in case it is disabled.
|
||||
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when registering the routes, and then simply don't registering the routes in case it is disabled.
|
||||
|
||||
File based migrators can always be enabled.
|
||||
|
||||
### Making the migrator public in `/info`
|
||||
### Making the migrator public in `/info`
|
||||
|
||||
You should make your migrator available in the `/info` endpoint so that frontends can display options to enable them or not.
|
||||
To do this, add an entry to the `AvailableMigrators` field in `pkg/routes/api/v1/info.go`.
|
||||
|
@ -10,7 +10,7 @@ menu:
|
||||
|
||||
# Notifications
|
||||
|
||||
Vikunjs provides a simple abstraction to send notifications per mail and in the database.
|
||||
Vikunja provides a simple abstraction to send notifications per mail and in the database.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
@ -39,7 +39,7 @@ A list of chainable functions is available to compose a mail:
|
||||
mail := NewMail().
|
||||
// The optional sender of the mail message.
|
||||
From("test@example.com").
|
||||
// The optional receipient of the mail message. Uses the mail address of the notifiable if omitted.
|
||||
// The optional recipient of the mail message. Uses the mail address of the notifiable if omitted.
|
||||
To("test@otherdomain.com").
|
||||
// The subject of the mail to send.
|
||||
Subject("Testmail").
|
||||
@ -49,7 +49,7 @@ mail := NewMail().
|
||||
Line("This is a line of text").
|
||||
// An action can contain a title and a url. It gets rendered as a big button in the mail.
|
||||
// Note that you can have only one action per mail.
|
||||
// All lines added before an action will appearr in the mail before the button, all lines
|
||||
// All lines added before an action will appear in the mail before the button, all lines
|
||||
// added afterwards will appear after it.
|
||||
Action("The Action", "https://example.com").
|
||||
// Another line of text.
|
||||
@ -60,8 +60,7 @@ If not provided, the `from` field of the mail contains the value configured in [
|
||||
|
||||
### Database notifications
|
||||
|
||||
All data returned from the `ToDB()` method is serialized to json and saved into the database, along with the id of the
|
||||
notifiable, the name of the notification and a time stamp.
|
||||
All data returned from the `ToDB()` method is serialized to json and saved into the database, along with the id of the notifiable, the name of the notification and a time stamp.
|
||||
If you don't use the database notification, the `Name()` function can return an empty string.
|
||||
|
||||
## Creating a new notification
|
||||
|
@ -12,10 +12,10 @@ menu:
|
||||
This checklist is a collection of all steps usually involved when releasing a new version of Vikunja.
|
||||
Not all steps are necessary for every release.
|
||||
|
||||
* Website update :
|
||||
* Website update
|
||||
* New Features: If there are new features worth mentioning the feature page should be updated.
|
||||
* New Screenshots: If an overhaul of an existing feature happend so that it now looks different from the existing screenshot, a new one is required.
|
||||
* Generate changelogs: (with git-cliff)
|
||||
* New Screenshots: If an overhaul of an existing feature happened so that it now looks different from the existing screenshot, a new one is required.
|
||||
* Generate changelogs (with git-cliff)
|
||||
* Frontend
|
||||
* API
|
||||
* Desktop
|
||||
@ -23,11 +23,11 @@ Not all steps are necessary for every release.
|
||||
* Frontend
|
||||
* API
|
||||
* Desktop
|
||||
* Once built: Prune the cloudflare cache so that the new versions show up at dl.vikunja.io
|
||||
* Release Highlights Blogpost:
|
||||
* Once built: Prune the cloudflare cache so that the new versions show up at [dl.vikunja.io](https://dl.vikunja.io/)
|
||||
* Release Highlights Blogpost
|
||||
* Include a section about Vikunja in general (totally fine to copy one from the earlier blog posts)
|
||||
* New Features & Improvements: Mention bigger features, potentially with screenshots. Things like refactoring are sometimes also worth mentioneing.
|
||||
* Publish:
|
||||
* New Features & Improvements: Mention bigger features, potentially with screenshots. Things like refactoring are sometimes also worth mentioning.
|
||||
* Publish
|
||||
* Reddit
|
||||
* Twitter
|
||||
* Mastodon
|
||||
@ -36,4 +36,3 @@ Not all steps are necessary for every release.
|
||||
* Forum
|
||||
* If features in the release were sponsored, send an email to relevant stakeholders
|
||||
* Update Vikunja Cloud version and other instances
|
||||
|
||||
|
@ -64,7 +64,7 @@ See [integration tests]({{< ref "test.md" >}}#integration-tests) for more detail
|
||||
|
||||
### log
|
||||
|
||||
Similar to `config`, this will set up the logging, based on differen logging backends.
|
||||
Similar to `config`, this will set up the logging, based on different logging backends.
|
||||
This init is called in `main.go` after the config init is done.
|
||||
|
||||
### mail
|
||||
@ -126,7 +126,7 @@ See [writing a migrator]({{< ref "migration.md" >}}).
|
||||
### red (redis)
|
||||
|
||||
This package initializes a connection to a redis server.
|
||||
This inizialization is automatically done at the startup of vikunja.
|
||||
This initialization is automatically done at the startup of Vikunja.
|
||||
|
||||
It also has a function (`GetRedis()`) which returns a redis client object you can then use in your package
|
||||
to talk to redis.
|
||||
@ -138,7 +138,7 @@ In most cases, using the `keyvalue` package is a better fit.
|
||||
|
||||
### routes
|
||||
|
||||
This package defines all routes which are available for vikunja clients to use.
|
||||
This package defines all routes which are available for Vikunja clients to use.
|
||||
To add a new route, see [adding a new route]({{< ref "feature.md">}}).
|
||||
|
||||
#### api/v1
|
||||
@ -162,10 +162,9 @@ A small package, containing some helper functions:
|
||||
* `MakeRandomString`: Generates a random string of a given length.
|
||||
* `Sha256`: Calculates a sha256 hash from a given string.
|
||||
|
||||
See their function definitions for instructions on how to use them.
|
||||
See their function definitions for instructions on how to use them.
|
||||
|
||||
### version
|
||||
|
||||
The single purpouse of this package is to hold the current vikunja version which gets overridden through build flags
|
||||
each time `mage release` or `mage build` is run.
|
||||
It is a seperate package to avoid import cycles with other packages.
|
||||
The single purpose of this package is to hold the current Vikunja version which gets overridden through build flags each time `mage release` or `mage build` is run.
|
||||
It is a separate package to avoid import cycles with other packages.
|
||||
|
@ -54,7 +54,7 @@ type Project struct {
|
||||
IsFavorite bool `xorm:"-" json:"is_favorite"`
|
||||
|
||||
// The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.
|
||||
// Will only returned when retreiving one project.
|
||||
// Will only returned when retrieving one project.
|
||||
Subscription *Subscription `xorm:"-" json:"subscription,omitempty"`
|
||||
|
||||
// The position this project has when querying all projects. See the tasks.position property on how to use this.
|
||||
|
@ -46,10 +46,10 @@ To run integration tests, use `mage test:integration`.
|
||||
|
||||
### Running tests with config
|
||||
|
||||
You can run tests with all available config variables if you want, enabeling you to run tests for a lot of scenarios.
|
||||
You can run tests with all available config variables if you want, enabling you to run tests for a lot of scenarios.
|
||||
We use this in CI to run all tests with different databases.
|
||||
|
||||
To use the normal config set the enviroment variable `VIKUNJA_TESTS_USE_CONFIG=1`.
|
||||
To use the normal config set the environment variable `VIKUNJA_TESTS_USE_CONFIG=1`.
|
||||
|
||||
### Showing sql queries
|
||||
|
||||
|
@ -21,7 +21,7 @@ Currently, only the frontend (and by extension, the desktop app) is translatable
|
||||
|
||||
## Translation Instructions
|
||||
|
||||
> These are the instructions for translating Vikunja in another language.
|
||||
> These are the instructions for translating Vikunja in another language.
|
||||
> For information about how to add new translation strings, see below.
|
||||
|
||||
For all languages these translation guidelines should be applied when translating:
|
||||
|
Reference in New Issue
Block a user