mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
Merge remote-tracking branch 'origin/master' into listener_refactor.4
This commit is contained in:
commit
cfe991a335
@ -47,5 +47,6 @@ archive:
|
||||
- languages/*.yaml
|
||||
- languages/*.json
|
||||
- languages/*.md
|
||||
wrap_in_directory: true
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}-{{ .Version }}-checksums.txt"
|
||||
|
39
CHANGELOG.md
39
CHANGELOG.md
@ -1,8 +1,24 @@
|
||||
# Changelog
|
||||
All notable changes to Oragono will be documented in this file.
|
||||
|
||||
## [1.1.0-rc1] - 2019-06-11
|
||||
We're pleased to be publishing the release candidate for 1.1.0 (the official release should follow in a week or two, with more complete credits). This version has a number of exciting improvements, including:
|
||||
## Unreleased
|
||||
New release of Oragono!
|
||||
|
||||
### Config Changes
|
||||
|
||||
### Security
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
## [1.1.0] - 2019-06-27
|
||||
We're pleased to announce Oragono version 1.1.0. This version has a number of exciting improvements, including:
|
||||
|
||||
* Simplified commands for registering new accounts with NickServ.
|
||||
* Support for IP cloaking.
|
||||
@ -10,22 +26,29 @@ We're pleased to be publishing the release candidate for 1.1.0 (the official rel
|
||||
* Support for the newly ratified [message tags](https://ircv3.net/specs/extensions/message-tags.html) and [message ID](https://ircv3.net/specs/extensions/message-ids.html) IRCv3 specifications; client developers are invited to use Oragono as a reference when implementing these specifications.
|
||||
* Support for running Oragono as a Tor hidden service.
|
||||
|
||||
This release includes a database change. If you have `datastore.autoupgrade` set to `true` in your configuration, it will be automatically applied when you restart Oragono; otherwise, you can update the database manually by running `oragono upgradedb`.
|
||||
Many thanks to [@Ascrod](https://github.com/Ascrod), [@amyspark](https://github.com/amyspark), [@bogdomania](https://github.com/bogdomania), [@csmith](https://github.com/csmith), [@jesopo](https://github.com/jesopo), [@jwheare](https://github.com/jwheare), lover, and [@transitracer](https://github.com/oragono/oragono/issues/456) for reporting issues and contributing patches, and also to [@bogdomania](https://github.com/bogdomania), Elvedin Hušić, Nuve, and [@streaps](https://github.com/streaps) for contributing translations.
|
||||
|
||||
### Upgrade notes
|
||||
|
||||
This release includes a database change. If you have `datastore.autoupgrade` set to `true` in your configuration, it will be automatically applied when you restart Oragono. Otherwise, you can update the database manually by running `oragono upgradedb` (see the manual for complete instructions).
|
||||
|
||||
No changes to your configuration file should be required for this upgrade. However, updating the file is necessary to enable some new functionality, as described below.
|
||||
|
||||
### Config changes
|
||||
* `tor-listeners` section added for configuring listeners for use with Tor.
|
||||
* `compatibility` section added for toggling compatibility behaviors for legacy clients.
|
||||
* `ip-cloaking` section added for configuring cloaking.
|
||||
* `bouncer` section added for configuring bouncer-like features (in particular, whether multiple clients can use the same nickname).
|
||||
* `check-ident` now defaults to `false`.
|
||||
* `nick-reservation.method` now defaults to `"strict"`.
|
||||
* `fakelag.enabled` now defaults to `true`
|
||||
* `check-ident` now has recommended value `false`.
|
||||
* `nick-reservation.method` now has recommended value "strict"`.
|
||||
* `fakelag.enabled` now has recommended value `true`.
|
||||
* `limits.linelen.tags` removed due to ratification of the [message-tags spec](https://ircv3.net/specs/extensions/message-tags.html), which fixes the maximum tags length at 8191 bytes.
|
||||
* `limits.registration-messages` added to restrict how many messages a user can send to the server during connection registration (while connecting to the server).
|
||||
* `channels.operator-only-creation` added to optionally restrict creation of new channels to ircops (#537).
|
||||
|
||||
### Security
|
||||
* Users can no longer impersonate network services like ChanServ by using confusing nicks like "ChɑnServ" (#519, thanks [@csmith](https://github.com/csmith)!).
|
||||
* Closed several loopholes in confusable nick detection (#562, #564, #570, thanks lover!)
|
||||
* Secret channels (mode `+s`) now act more secret (#380, thanks [@csmith](https://github.com/csmith)!).
|
||||
* The `+R` (registered-only) mode now prevents unregistered users from joining the channel, not just from speaking (#463, thanks [@bogdomania](https://github.com/bogdomania)!).
|
||||
* Limited how many messages clients can send during connection registration to mitigate potential DoS attacks (#505).
|
||||
@ -59,6 +82,8 @@ This release includes a database change. If you have `datastore.autoupgrade` set
|
||||
* Support for the [draft/event-playback](https://github.com/DanielOaks/ircv3-specifications/blob/master+event-playback/extensions/batch/history.md) spec (#457).
|
||||
* The `TAGMSG` and `NICK` messages are now replayable in history (#457).
|
||||
* Added the draft IRCv3 [`SETNAME` command](https://ircv3.net/specs/extensions/setname) for changing your realname (#372).
|
||||
* Added new Bosnian (bs-BA) translation (thanks to Elvedin Hušić!).
|
||||
* Added new German (de-DE) translation (thanks to streaps!).
|
||||
|
||||
### Changed
|
||||
* Registering an account with NickServ is now `/msg NickServ register <password>`, which registers the current nickname as an account, matching other services (#410).
|
||||
@ -79,6 +104,8 @@ This release includes a database change. If you have `datastore.autoupgrade` set
|
||||
* `NICKSERV ENFORCE` is deprecated in favor of the new `NICKSERV SET ENFORCE` (the old syntax is still available as an alias).
|
||||
* The `WHO` command is now treated like `PONG` in that it doesn't count as user activity, since client software often uses it automatically (#485).
|
||||
* The `NAMES` command now only returns results for the first given channel (#534).
|
||||
* Updated French (fr-FR) translation (thanks to Nuve!).
|
||||
* Updated Română (ro-RO) translation (thanks to [@bogdomania](https://github.com/bogdomania)!).
|
||||
|
||||
### Internal Notes
|
||||
* Building Oragono is now easier (#409).
|
||||
|
4
Gopkg.lock
generated
4
Gopkg.lock
generated
@ -62,11 +62,11 @@
|
||||
revision = "9520e82c474b0a04dd04f8a40959027271bab992"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7caf3ea977a13cd8b9a2e1ecef1ccaa8e38f831b4f6ffcb8bd0aa909c48afb3a"
|
||||
digest = "1:e7de6e4830c9d4fe1463c09a2ee15ec3eb9455c2ea916044675c413e8a9c6608"
|
||||
name = "github.com/oragono/confusables"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d5dd03409482fae2457f0742be22782890f720c2"
|
||||
revision = "fe1cf31a24b01cac37194669863df51713e08e54"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -50,7 +50,7 @@
|
||||
name = "github.com/oragono/go-ident"
|
||||
|
||||
[[constraint]]
|
||||
revision = "d5dd03409482fae2457f0742be22782890f720c2"
|
||||
revision = "fe1cf31a24b01cac37194669863df51713e08e54"
|
||||
name = "github.com/oragono/confusables"
|
||||
|
||||
[[constraint]]
|
||||
|
2
Makefile
2
Makefile
@ -7,7 +7,7 @@ all: install
|
||||
install: deps
|
||||
./install.sh
|
||||
|
||||
release:
|
||||
release: deps
|
||||
goreleaser --skip-publish --rm-dist
|
||||
|
||||
capdefs:
|
||||
|
@ -22,6 +22,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
|
||||
- Installing
|
||||
- Windows
|
||||
- macOS / Linux / Raspberry Pi
|
||||
- Upgrading
|
||||
- Features
|
||||
- User Accounts
|
||||
- Nickname reservation
|
||||
@ -135,6 +136,8 @@ On a non-systemd system, oragono can be configured to log to a file and used [lo
|
||||
|
||||
As long as you are using official releases or release candidates of Oragono, any backwards-incompatible changes should be described in the changelog.
|
||||
|
||||
In general, the config file format should be fully backwards and forwards compatible. Unless otherwise noted, no config file changes should be necessary when upgrading Oragono. However, the "config changes" section of the changelog will typically describe new sections that can be added to your config to enable new functionality, as well as changes in the recommended values of certain fields.
|
||||
|
||||
The database is versioned; upgrades that involve incompatible changes to the database require updating the database. If you have `datastore.autoupgrade` enabled in your config, the database will be backed up and upgraded when you restart your server when required. Otherwise, you can apply upgrades manually:
|
||||
|
||||
1. Stop your server
|
||||
|
@ -77,7 +77,7 @@ CAPDEFS = [
|
||||
),
|
||||
CapDef(
|
||||
identifier="LabeledResponse",
|
||||
name="draft/labeled-response",
|
||||
name="draft/labeled-response-0.2",
|
||||
url="https://ircv3.net/specs/extensions/labeled-response.html",
|
||||
standard="draft IRCv3",
|
||||
),
|
||||
|
@ -53,7 +53,7 @@ const (
|
||||
// https://ircv3.net/specs/extensions/invite-notify-3.2.html
|
||||
InviteNotify Capability = iota
|
||||
|
||||
// LabeledResponse is the draft IRCv3 capability named "draft/labeled-response":
|
||||
// LabeledResponse is the draft IRCv3 capability named "draft/labeled-response-0.2":
|
||||
// https://ircv3.net/specs/extensions/labeled-response.html
|
||||
LabeledResponse Capability = iota
|
||||
|
||||
@ -135,7 +135,7 @@ var (
|
||||
"echo-message",
|
||||
"extended-join",
|
||||
"invite-notify",
|
||||
"draft/labeled-response",
|
||||
"draft/labeled-response-0.2",
|
||||
"draft/languages",
|
||||
"oragono.io/maxline-2",
|
||||
"message-tags",
|
||||
|
@ -1160,12 +1160,7 @@ func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Ti
|
||||
msg.SetTag("msgid", msgid)
|
||||
}
|
||||
// attach server-time
|
||||
if session.capabilities.Has(caps.ServerTime) {
|
||||
if serverTime.IsZero() {
|
||||
serverTime = time.Now().UTC()
|
||||
}
|
||||
msg.SetTag("time", serverTime.Format(IRCv3TimestampFormat))
|
||||
}
|
||||
session.setTimeTag(&msg, serverTime)
|
||||
|
||||
return session.SendRawMessage(msg, blocking)
|
||||
}
|
||||
@ -1246,12 +1241,19 @@ func (client *Client) Send(tags map[string]string, prefix string, command string
|
||||
|
||||
func (session *Session) Send(tags map[string]string, prefix string, command string, params ...string) (err error) {
|
||||
msg := ircmsg.MakeMessage(tags, prefix, command, params...)
|
||||
if session.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
|
||||
msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
session.setTimeTag(&msg, time.Time{})
|
||||
return session.SendRawMessage(msg, false)
|
||||
}
|
||||
|
||||
func (session *Session) setTimeTag(msg *ircmsg.IrcMessage, serverTime time.Time) {
|
||||
if session.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
|
||||
if serverTime.IsZero() {
|
||||
serverTime = time.Now()
|
||||
}
|
||||
msg.SetTag("time", serverTime.UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
}
|
||||
|
||||
// Notice sends the client a notice from the server.
|
||||
func (client *Client) Notice(text string) {
|
||||
client.Send(nil, client.server.name, "NOTICE", client.Nick(), text)
|
||||
|
@ -9,7 +9,7 @@ import "fmt"
|
||||
|
||||
const (
|
||||
// SemVer is the semantic version of Oragono.
|
||||
SemVer = "1.1.0-rc1"
|
||||
SemVer = "1.2.0-unreleased"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -93,9 +93,7 @@ func (rb *ResponseBuffer) AddFromClient(time time.Time, msgid string, fromNickMa
|
||||
msg.SetTag("msgid", msgid)
|
||||
}
|
||||
// attach server-time
|
||||
if rb.session.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
|
||||
msg.SetTag("time", time.UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
rb.session.setTimeTag(&msg, time)
|
||||
|
||||
rb.AddMessage(msg)
|
||||
}
|
||||
@ -212,24 +210,28 @@ func (rb *ResponseBuffer) flushInternal(final bool, blocking bool) error {
|
||||
}
|
||||
|
||||
useLabel := rb.session.capabilities.Has(caps.LabeledResponse) && rb.Label != ""
|
||||
// use a batch if we have a label, and we either currently have 0 or 2+ messages,
|
||||
// use a batch if we have a label, and we either currently have 2+ messages,
|
||||
// or we are doing a Flush() and we have to assume that there will be more messages
|
||||
// in the future.
|
||||
useBatch := useLabel && (len(rb.messages) != 1 || !final)
|
||||
startBatch := useLabel && (1 < len(rb.messages) || !final)
|
||||
|
||||
// if label but no batch, add label to first message
|
||||
if useLabel && !useBatch && len(rb.messages) == 1 && rb.batchID == "" {
|
||||
rb.messages[0].SetTag(caps.LabelTagName, rb.Label)
|
||||
} else if useBatch {
|
||||
if startBatch {
|
||||
rb.sendBatchStart(blocking)
|
||||
} else if useLabel && len(rb.messages) == 0 && rb.batchID == "" && final {
|
||||
// ACK message
|
||||
message := ircmsg.MakeMessage(nil, rb.session.client.server.name, "ACK")
|
||||
message.SetTag(caps.LabelTagName, rb.Label)
|
||||
rb.session.setTimeTag(&message, time.Time{})
|
||||
rb.session.SendRawMessage(message, blocking)
|
||||
} else if useLabel && len(rb.messages) == 1 && rb.batchID == "" && final {
|
||||
// single labeled message
|
||||
rb.messages[0].SetTag(caps.LabelTagName, rb.Label)
|
||||
}
|
||||
|
||||
// send each message out
|
||||
for _, message := range rb.messages {
|
||||
// attach server-time if needed
|
||||
if rb.session.capabilities.Has(caps.ServerTime) && !message.HasTag("time") {
|
||||
message.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
|
||||
}
|
||||
rb.session.setTimeTag(&message, time.Time{})
|
||||
|
||||
// attach batch ID, unless this message was part of a nested batch and is
|
||||
// already tagged
|
||||
|
@ -108,26 +108,6 @@ func CasefoldName(name string) (string, error) {
|
||||
return lowered, err
|
||||
}
|
||||
|
||||
// "boring" names are exempt from skeletonization.
|
||||
// this is because confusables.txt considers various pure ASCII alphanumeric
|
||||
// strings confusable: 0 and O, 1 and l, m and rn. IMO this causes more problems
|
||||
// than it solves.
|
||||
func isBoring(name string) bool {
|
||||
for i := 0; i < len(name); i += 1 {
|
||||
chr := name[i]
|
||||
if (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') {
|
||||
continue // alphanumerics
|
||||
}
|
||||
switch chr {
|
||||
case '$', '%', '^', '&', '(', ')', '{', '}', '[', ']', '<', '>', '=':
|
||||
continue // benign printable ascii characters
|
||||
default:
|
||||
return false // potentially confusable ascii like | ' `, non-ascii
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// returns true if the given name is a valid ident, using a mix of Insp and
|
||||
// Chary's ident restrictions.
|
||||
func isIdent(name string) bool {
|
||||
@ -168,9 +148,7 @@ func Skeleton(name string) (string, error) {
|
||||
// same as PRECIS:
|
||||
name = width.Fold.String(name)
|
||||
|
||||
if !isBoring(name) {
|
||||
name = confusables.Skeleton(name)
|
||||
}
|
||||
name = confusables.SkeletonTweaked(name)
|
||||
|
||||
// internationalized lowercasing for skeletons; this is much more lenient than
|
||||
// Casefold. In particular, skeletons are expected to mix scripts (which may
|
||||
|
@ -128,18 +128,6 @@ func TestCasefoldName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBoring(t *testing.T) {
|
||||
assertBoring := func(str string, expected bool) {
|
||||
if isBoring(str) != expected {
|
||||
t.Errorf("expected [%s] to have boringness [%t], but got [%t]", str, expected, !expected)
|
||||
}
|
||||
}
|
||||
|
||||
assertBoring("warning", true)
|
||||
assertBoring("phi|ip", false)
|
||||
assertBoring("Νικηφόρος", false)
|
||||
}
|
||||
|
||||
func TestIsIdent(t *testing.T) {
|
||||
assertIdent := func(str string, expected bool) {
|
||||
if isIdent(str) != expected {
|
||||
@ -173,15 +161,15 @@ func TestSkeleton(t *testing.T) {
|
||||
t.Errorf("but we still consider pipe confusable with l")
|
||||
}
|
||||
|
||||
if skeleton("smt") != "smt" {
|
||||
if skeleton("smt") != skeleton("smt") {
|
||||
t.Errorf("fullwidth characters should skeletonize to plain old ascii characters")
|
||||
}
|
||||
|
||||
if skeleton("SMT") != "smt" {
|
||||
if skeleton("SMT") != skeleton("smt") {
|
||||
t.Errorf("after skeletonizing, we should casefold")
|
||||
}
|
||||
|
||||
if skeleton("smt") != "smt" {
|
||||
if skeleton("smt") != skeleton("smt") {
|
||||
t.Errorf("our friend lover successfully tricked the skeleton algorithm!")
|
||||
}
|
||||
|
||||
@ -189,6 +177,10 @@ func TestSkeleton(t *testing.T) {
|
||||
t.Errorf("we must protect against cyrillic homoglyph attacks")
|
||||
}
|
||||
|
||||
if skeleton("еmily") != skeleton("emily") {
|
||||
t.Errorf("we must protect against cyrillic homoglyph attacks")
|
||||
}
|
||||
|
||||
if skeleton("РОТАТО") != "potato" {
|
||||
t.Errorf("we must protect against cyrillic homoglyph attacks")
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"$bWarning: unregistering this account will remove its stored privileges.$b": "$bAttention : effacer ce compte lèvera ses privilèges.$b",
|
||||
"$bWarning: unregistering this channel will remove all stored channel attributes.$b": "$bAttention : effacer ce canal le démunira de ses attributs.$b",
|
||||
"%[1]d. User %[2]s requests vhost: %[3]s": "",
|
||||
"%[1]s [account: %[2]s] joined the channel": "",
|
||||
"%[1]d. User %[2]s requests vhost: %[3]s": "%[1]d. %[2]s demande l’hôtel virtuel : %[3]s",
|
||||
"%[1]s [account: %[2]s] joined the channel": "%[1]s [%[2]s] a rejoint le canal",
|
||||
"%[1]s changed nick to %[2]s": "%[1]s a changé de nom pour %[2]s",
|
||||
"%[1]s kicked %[2]s (%[3]s)": "%[1]s a éjecté %[2]s (%[3]s)",
|
||||
"%[1]s left the channel (%[2]s)": "%[1]s a quitté le canal (%[2]s)",
|
||||
"%[1]s quit (%[2]s)": "",
|
||||
"%[1]s quit (%[2]s)": "%[1]s est parti·e (%[2]s)",
|
||||
"%s joined the channel": "%s a rejoint le canal",
|
||||
"*** $bEnd of %s HELP$b ***": "",
|
||||
"*** $bEnd of %s HELP$b ***": "*** $bFin de %s AIDE$b ***",
|
||||
"*** Could not find your username": "*** Impossible de trouver votre nom d’utilisateurice",
|
||||
"*** Found your username": "*** Nom d’utilisateurice trouvé",
|
||||
"*** Got a malformed username, ignoring": "*** Reçu mauvais nom d’utilisateurice",
|
||||
@ -42,7 +42,7 @@
|
||||
"Bouncer functionality is currently disabled for your account, but you can opt in": "",
|
||||
"Bouncer functionality is currently enabled for your account": "",
|
||||
"Bouncer functionality is currently enabled for your account, but you can opt out": "",
|
||||
"CTCP messages are disabled over Tor": "",
|
||||
"CTCP messages are disabled over Tor": "Les messages CTCP sont désactivés via Tor",
|
||||
"Can't change modes for other users": "Impossible de changer les modes d’autres utilisateurices",
|
||||
"Can't view modes for other users": "Impossible de voir les modes des autres utilisateurices",
|
||||
"Cannot join channel (+%s)": "Impossible de joindre (+%s)",
|
||||
@ -57,7 +57,7 @@
|
||||
"Channel %[1]s has %[2]d persistent modes set": "",
|
||||
"Channel %s is now unregistered": "",
|
||||
"Channel %s successfully registered": "",
|
||||
"Channel does not exist": "",
|
||||
"Channel does not exist": "Ce canal n’existe pas",
|
||||
"Channel doesn't have roleplaying mode available": "",
|
||||
"Channel is not registered": "",
|
||||
"Channel list is full": "La liste de canaux est pleine",
|
||||
@ -91,11 +91,11 @@
|
||||
"End of list": "Fin de liste",
|
||||
"Erroneous nickname": "Nom inadéquat",
|
||||
"Error loading account data": "",
|
||||
"Error reserving nickname": "",
|
||||
"Error reserving nickname": "Erreur lors de la réservation du nom",
|
||||
"Error while unregistering account": "Erreur au cours de la suppression du compte",
|
||||
"Fake source must be a valid nickname": "",
|
||||
"First param must be a mask or channel": "",
|
||||
"GHOSTed by %s": "",
|
||||
"GHOSTed by %s": "Déconnecté·e via Ghost par %s",
|
||||
"Given current server settings, your nickname is enforced with: %s": "",
|
||||
"HELPOP <argument>\n\nGet an explanation of <argument>, or \"index\" for a list of help topics.": "",
|
||||
"Help not found": "Aide introuvable",
|
||||
@ -103,10 +103,10 @@
|
||||
"I have %[1]d clients and %[2]d servers": "J’ai %[1]d client(s), et %[2]d serveur(s)",
|
||||
"I'll be right back": "De retour dans un instant !",
|
||||
"IP address: %s": "Adresse IP : %s",
|
||||
"IRC Operators online": "",
|
||||
"Input line too long": "",
|
||||
"Insufficient oper privs": "",
|
||||
"Insufficient privileges": "",
|
||||
"IRC Operators online": "Opérateurices IRC en ligne",
|
||||
"Input line too long": "Entrée trop longue",
|
||||
"Insufficient oper privs": "Privilèges opérateurices insuffisants",
|
||||
"Insufficient privileges": "Privilèges insuffisants",
|
||||
"Internal error": "Erreur interne",
|
||||
"Invalid CAP subcommand": "",
|
||||
"Invalid account name": "Nom de compte invalide",
|
||||
@ -115,11 +115,11 @@
|
||||
"Invalid vhost": "Vhost invalide",
|
||||
"It was rejected for reason: %s": "",
|
||||
"JOIN 0 is not allowed": "",
|
||||
"Language %s is not supported by this server": "",
|
||||
"Language preferences have been set": "",
|
||||
"Last active: %s": "",
|
||||
"Language %s is not supported by this server": "Le langage %s n’est pas proposé sur ce serveur",
|
||||
"Language preferences have been set": "Vos préférences linguistiques ont été enregistrées",
|
||||
"Last active: %s": "Dernière activité : %s",
|
||||
"MOTD File is missing": "Message du jour manquant",
|
||||
"Malformed username": "",
|
||||
"Malformed username": "Nom erroné",
|
||||
"Mask isn't valid": "Masque invalide",
|
||||
"Must register with current nickname instead of separate account name": "",
|
||||
"Network service, for more info /msg %s HELP": "",
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
name: "Français"
|
||||
code: "fr-FR"
|
||||
contributors: "Joshua Kwan <joshk@triplehelix.org>"
|
||||
contributors: "Joshua Kwan <joshk@triplehelix.org>, Nuve"
|
||||
incomplete: true
|
||||
|
2
vendor
2
vendor
@ -1 +1 @@
|
||||
Subproject commit 8ddbb531841add50f8b7aff8fe00bef311448aaa
|
||||
Subproject commit 6fb1b63d24a6ccc28a8aecbd47a7eb18a2ace2c9
|
Loading…
Reference in New Issue
Block a user