Commit 44914394 authored by Wilko Manger's avatar Wilko Manger

Refactor localizations, use intl package

parent f879bf41
flutter pub run intl_translation:extract_to_arb \
--output-dir=lib/src/resources/intl lib/src/resources/intl/localizations.dart
mv lib/src/resources/intl/intl_messages.arb lib/src/resources/intl/intl_en.arb
flutter pub run intl_translation:generate_from_arb \
--output-dir=lib/src/resources/intl --no-use-deferred-loading \
lib/src/resources/intl/localizations.dart lib/src/resources/intl/*.arb
......@@ -26,7 +26,7 @@ import 'package:provider/provider.dart';
import 'auth/bloc.dart';
import 'matrix.dart';
import 'notifications/bloc.dart';
import 'resources/localizations.dart';
import 'resources/intl/localizations.dart';
import 'resources/theme.dart';
import 'section/main/chat/page.dart';
import 'section/main/chat/image/page.dart';
......@@ -156,9 +156,9 @@ class App extends StatelessWidget {
),
child: MaterialApp(
onGenerateTitle: (BuildContext context) =>
l(context).appName,
context.intl.appName,
localizationsDelegates: [
const AppLocalizationsDelegate(),
const PattleLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
......
{
"@@last_modified": "2020-03-22T18:31:30.071193",
"appName": "Pattle",
"@appName": {
"type": "text",
"placeholders": {}
},
"_Common_name": "Name",
"@_Common_name": {
"type": "text",
"placeholders": {}
},
"_Common_username": "Username",
"@_Common_username": {
"type": "text",
"placeholders": {}
},
"_Common_password": "Password",
"@_Common_password": {
"type": "text",
"placeholders": {}
},
"_Common_confirm": "Confirm",
"@_Common_confirm": {
"type": "text",
"placeholders": {}
},
"_Common_next": "Next",
"@_Common_next": {
"type": "text",
"placeholders": {}
},
"_Common_photo": "Photo",
"@_Common_photo": {
"type": "text",
"placeholders": {}
},
"_Common_you": "You",
"@_Common_you": {
"description": "Used to denote the user using the app instead of their name",
"type": "text",
"placeholders": {}
},
"_Start_advanced": "Advanced",
"@_Start_advanced": {
"type": "text",
"placeholders": {}
},
"_Start_login": "Login",
"@_Start_login": {
"type": "text",
"placeholders": {}
},
"_Start_register": "Register",
"@_Start_register": {
"type": "text",
"placeholders": {}
},
"_Start_homeserver": "Homeserver",
"@_Start_homeserver": {
"type": "text",
"placeholders": {}
},
"_Start_identityServer": "Identity server",
"@_Start_identityServer": {
"type": "text",
"placeholders": {}
},
"_Start_loginWithPhone": "Login with phone number",
"@_Start_loginWithPhone": {
"type": "text",
"placeholders": {}
},
"_Start_loginWithEmail": "Login with email",
"@_Start_loginWithEmail": {
"type": "text",
"placeholders": {}
},
"_Start_loginWithUsername": "Login with username",
"@_Start_loginWithUsername": {
"type": "text",
"placeholders": {}
},
"_StartUsername_title": "Enter username",
"@_StartUsername_title": {
"type": "text",
"placeholders": {}
},
"_StartUsername_usernameInvalidError": "Invalid username. May only contain letters, numbers, -, ., =, _ and /",
"@_StartUsername_usernameInvalidError": {
"type": "text",
"placeholders": {}
},
"_StartUsername_userIdInvalidError": "Invalid user ID. Must be in the format of '@name:server.tld'",
"@_StartUsername_userIdInvalidError": {
"type": "text",
"placeholders": {}
},
"_StartUsername_hostnameInvalidError": "Invalid hostname",
"@_StartUsername_hostnameInvalidError": {
"type": "text",
"placeholders": {}
},
"_StartUsername_unknownError": "An unknown error occured",
"@_StartUsername_unknownError": {
"type": "text",
"placeholders": {}
},
"_StartUsername_wrongPasswordError": "Wrong password. Please try again",
"@_StartUsername_wrongPasswordError": {
"type": "text",
"placeholders": {}
},
"_Chat_typeAMessage": "Type a message",
"@_Chat_typeAMessage": {
"description": "Hint for the chat input",
"type": "text",
"placeholders": {}
},
"_Chat_cantSendMessages": "You can't send messages to this group because you're no longer a participant.",
"@_Chat_cantSendMessages": {
"type": "text",
"placeholders": {}
},
"_Chat_typing": "typing...",
"@_Chat_typing": {
"type": "text",
"placeholders": {}
},
"_Chat_isTyping": "{name} is typing...",
"@_Chat_isTyping": {
"type": "text",
"placeholders": {
"name": {}
}
},
"_Chat_areTyping": "{andMore,select, false{{first} and {second} are typing...}true{{first}, {second} and more are typing...}}",
"@_Chat_areTyping": {
"type": "text",
"placeholders": {
"andMore": {},
"first": {},
"second": {}
}
},
"_ChatMessage_deletion": "{person,select, second{You deleted this message}third{{name} deleted this message}}",
"@_ChatMessage_deletion": {
"type": "text",
"placeholders": {
"person": {},
"name": {}
}
},
"_ChatMessage_creation": "{person,select, second{You created this group}third{{name} created this group}}",
"@_ChatMessage_creation": {
"type": "text",
"placeholders": {
"person": {},
"name": {}
}
},
"_ChatMessage_upgrade": "{person,select, second{You upgraded this group}third{{name} upgraded this group}}",
"@_ChatMessage_upgrade": {
"type": "text",
"placeholders": {
"person": {},
"name": {}
}
},
"_ChatMessage_descriptionChange": "{person,select, second{You changed the description of this group}third{{name} changed the description of this group}}",
"@_ChatMessage_descriptionChange": {
"type": "text",
"placeholders": {
"person": {},
"name": {}
}
},
"_ChatMessage_join": "{person,select, second{You joined}third{{name} joined}}",
"@_ChatMessage_join": {
"type": "text",
"placeholders": {
"person": {},
"name": {}
}
},
"_ChatMessage_leave": "{person,select, second{You left}third{{name} left}}",
"@_ChatMessage_leave": {
"type": "text",
"placeholders": {
"person": {},
"name": {}
}
},
"_ChatMessage_ban": "{person,select, secondOnSecond{You were banned by yourself}secondOnThird{You were banned by {banner}}thirdOnThird{{bannee} was banned by {banner}}thirdOnSecond{{bannee} was banned by you}}",
"@_ChatMessage_ban": {
"type": "text",
"placeholders": {
"person": {},
"bannee": {},
"banner": {}
}
},
"_ChatMessage_invite": "{person,select, secondOnSecond{You were invited by yourself}secondOnThird{You were invited by {inviter}}thirdOnThird{{invitee} was invited by {inviter}}thirdOnSecond{{invitee} was invited by you}}",
"@_ChatMessage_invite": {
"type": "text",
"placeholders": {
"person": {},
"invitee": {},
"inviter": {}
}
},
"_ChatDetails_description": "Description",
"@_ChatDetails_description": {
"type": "text",
"placeholders": {}
},
"_ChatDetails_noDescriptionSet": "No description has been set",
"@_ChatDetails_noDescriptionSet": {
"type": "text",
"placeholders": {}
},
"_ChatDetails_more": "{count} more",
"@_ChatDetails_more": {
"type": "text",
"placeholders": {
"count": {}
}
},
"Participants": "Participants",
"@Participants": {
"type": "text",
"placeholders": {}
},
"_ChatDetails_participants": "{count,plural, =0{No participants}=1{{count} participant}other{{count} participants}}",
"@_ChatDetails_participants": {
"type": "text",
"placeholders": {
"count": {}
}
},
"_Chats_personal": "Personal",
"@_Chats_personal": {
"type": "text",
"placeholders": {}
},
"_Chats_public": "Public",
"@_Chats_public": {
"type": "text",
"placeholders": {}
},
"_ChatsNewGroup_title": "New group",
"@_ChatsNewGroup_title": {
"type": "text",
"placeholders": {}
},
"_ChatsNewGroup_groupName": "Group name",
"@_ChatsNewGroup_groupName": {
"type": "text",
"placeholders": {}
},
"_Settings_title": "Settings",
"@_Settings_title": {
"description": "Settings page title",
"type": "text",
"placeholders": {}
},
"_Settings_accountTileTitle": "Account",
"@_Settings_accountTileTitle": {
"type": "text",
"placeholders": {}
},
"_Settings_accountTileSubtitle": "Privacy, security, change password",
"@_Settings_accountTileSubtitle": {
"type": "text",
"placeholders": {}
},
"_Settings_appearanceTileTitle": "Appearance",
"@_Settings_appearanceTileTitle": {
"type": "text",
"placeholders": {}
},
"_Settings_appearanceTileSubtitle": "Theme, font size",
"@_Settings_appearanceTileSubtitle": {
"type": "text",
"placeholders": {}
},
"_Settings_brightnessTileTitle": "Brightness",
"@_Settings_brightnessTileTitle": {
"type": "text",
"placeholders": {}
},
"_Settings_brightnessTileOptionLight": "Light",
"@_Settings_brightnessTileOptionLight": {
"type": "text",
"placeholders": {}
},
"_Settings_brightnessTileOptionDark": "Dark",
"@_Settings_brightnessTileOptionDark": {
"type": "text",
"placeholders": {}
},
"Profile": "Profile",
"@Profile": {
"type": "text",
"placeholders": {}
},
"_Settings_editNameDescription": "This is not your username. This is the name that will be visible to others.",
"@_Settings_editNameDescription": {
"type": "text",
"placeholders": {}
},
"_Error_connectionLost": "Connection has been lost.\nMake sure your phone has an active internet connection.",
"@_Error_connectionLost": {
"type": "text",
"placeholders": {}
},
"_Error_connectionFailed": "Connection failed. Check your internet connection",
"@_Error_connectionFailed": {
"type": "text",
"placeholders": {}
},
"_Error_connectionFailedServerOverloaded": "Connection failed. The server is probably overloaded.",
"@_Error_connectionFailedServerOverloaded": {
"type": "text",
"placeholders": {}
},
"_Error_anErrorHasOccurred": "An error has occurred:",
"@_Error_anErrorHasOccurred": {
"description": "After this message the error is displayed (hence the colon)",
"type": "text",
"placeholders": {}
},
"_Error_thisErrorHasBeenReported": "This error has been reported. Please restart Pattle.",
"@_Error_thisErrorHasBeenReported": {
"type": "text",
"placeholders": {}
},
"_Time_today": "Today",
"@_Time_today": {
"type": "text",
"placeholders": {}
},
"_Time_yesterday": "Yesterday",
"@_Time_yesterday": {
"type": "text",
"placeholders": {}
}
}
\ No newline at end of file
This diff is collapsed.
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that looks up messages for specific locales by
// delegating to the appropriate library.
// Ignore issues from commonly used lints in this file.
// ignore_for_file:implementation_imports, file_names, unnecessary_new
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
// ignore_for_file:argument_type_not_assignable, invalid_assignment
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
// ignore_for_file:comment_references
import 'dart:async';
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
import 'package:intl/src/intl_helpers.dart';
import 'messages_en.dart' as messages_en;
typedef Future<dynamic> LibraryLoader();
Map<String, LibraryLoader> _deferredLibraries = {
'en': () => new Future.value(null),
};
MessageLookupByLibrary _findExact(String localeName) {
switch (localeName) {
case 'en':
return messages_en.messages;
default:
return null;
}
}
/// User programs should call this before using [localeName] for messages.
Future<bool> initializeMessages(String localeName) async {
var availableLocale = Intl.verifiedLocale(
localeName,
(locale) => _deferredLibraries[locale] != null,
onFailure: (_) => null);
if (availableLocale == null) {
return new Future.value(false);
}
var lib = _deferredLibraries[availableLocale];
await (lib == null ? new Future.value(false) : lib());
initializeInternalMessageLookup(() => new CompositeMessageLookup());
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
return new Future.value(true);
}
bool _messagesExistFor(String locale) {
try {
return _findExact(locale) != null;
} catch (e) {
return false;
}
}
MessageLookupByLibrary _findGeneratedMessagesFor(String locale) {
var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
onFailure: (_) => null);
if (actualLocale == null) return null;
return _findExact(actualLocale);
}
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
final messages = new MessageLookup();
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'en';
static m0(count) => "${count} more";
static m1(count) => "${Intl.plural(count, zero: 'No participants', one: '${count} participant', other: '${count} participants')}";
static m2(person, bannee, banner) => "${Intl.select(person, {'secondOnSecond': 'You were banned by yourself', 'secondOnThird': 'You were banned by ${banner}', 'thirdOnThird': '${bannee} was banned by ${banner}', 'thirdOnSecond': '${bannee} was banned by you', })}";
static m3(person, name) => "${Intl.select(person, {'second': 'You created this group', 'third': '${name} created this group', })}";
static m4(person, name) => "${Intl.select(person, {'second': 'You deleted this message', 'third': '${name} deleted this message', })}";
static m5(person, name) => "${Intl.select(person, {'second': 'You changed the description of this group', 'third': '${name} changed the description of this group', })}";
static m6(person, invitee, inviter) => "${Intl.select(person, {'secondOnSecond': 'You were invited by yourself', 'secondOnThird': 'You were invited by ${inviter}', 'thirdOnThird': '${invitee} was invited by ${inviter}', 'thirdOnSecond': '${invitee} was invited by you', })}";
static m7(person, name) => "${Intl.select(person, {'second': 'You joined', 'third': '${name} joined', })}";
static m8(person, name) => "${Intl.select(person, {'second': 'You left', 'third': '${name} left', })}";
static m9(person, name) => "${Intl.select(person, {'second': 'You upgraded this group', 'third': '${name} upgraded this group', })}";
static m10(andMore, first, second) => "${Intl.select(andMore, {'false': '${first} and ${second} are typing...', 'true': '${first}, ${second} and more are typing...', })}";
static m11(name) => "${name} is typing...";
final messages = _notInlinedMessages(_notInlinedMessages);
static _notInlinedMessages(_) => <String, Function> {
"Participants" : MessageLookupByLibrary.simpleMessage("Participants"),
"Profile" : MessageLookupByLibrary.simpleMessage("Profile"),
"_ChatDetails_description" : MessageLookupByLibrary.simpleMessage("Description"),
"_ChatDetails_more" : m0,
"_ChatDetails_noDescriptionSet" : MessageLookupByLibrary.simpleMessage("No description has been set"),
"_ChatDetails_participants" : m1,
"_ChatMessage_ban" : m2,
"_ChatMessage_creation" : m3,
"_ChatMessage_deletion" : m4,
"_ChatMessage_descriptionChange" : m5,
"_ChatMessage_invite" : m6,
"_ChatMessage_join" : m7,
"_ChatMessage_leave" : m8,
"_ChatMessage_upgrade" : m9,
"_Chat_areTyping" : m10,
"_Chat_cantSendMessages" : MessageLookupByLibrary.simpleMessage("You can\'t send messages to this group because you\'re no longer a participant."),
"_Chat_isTyping" : m11,
"_Chat_typeAMessage" : MessageLookupByLibrary.simpleMessage("Type a message"),
"_Chat_typing" : MessageLookupByLibrary.simpleMessage("typing..."),
"_ChatsNewGroup_groupName" : MessageLookupByLibrary.simpleMessage("Group name"),
"_ChatsNewGroup_title" : MessageLookupByLibrary.simpleMessage("New group"),
"_Chats_personal" : MessageLookupByLibrary.simpleMessage("Personal"),
"_Chats_public" : MessageLookupByLibrary.simpleMessage("Public"),
"_Common_confirm" : MessageLookupByLibrary.simpleMessage("Confirm"),
"_Common_name" : MessageLookupByLibrary.simpleMessage("Name"),
"_Common_next" : MessageLookupByLibrary.simpleMessage("Next"),
"_Common_password" : MessageLookupByLibrary.simpleMessage("Password"),
"_Common_photo" : MessageLookupByLibrary.simpleMessage("Photo"),
"_Common_username" : MessageLookupByLibrary.simpleMessage("Username"),
"_Common_you" : MessageLookupByLibrary.simpleMessage("You"),
"_Error_anErrorHasOccurred" : MessageLookupByLibrary.simpleMessage("An error has occurred:"),
"_Error_connectionFailed" : MessageLookupByLibrary.simpleMessage("Connection failed. Check your internet connection"),
"_Error_connectionFailedServerOverloaded" : MessageLookupByLibrary.simpleMessage("Connection failed. The server is probably overloaded."),
"_Error_connectionLost" : MessageLookupByLibrary.simpleMessage("Connection has been lost.\nMake sure your phone has an active internet connection."),
"_Error_thisErrorHasBeenReported" : MessageLookupByLibrary.simpleMessage("This error has been reported. Please restart Pattle."),
"_Settings_accountTileSubtitle" : MessageLookupByLibrary.simpleMessage("Privacy, security, change password"),
"_Settings_accountTileTitle" : MessageLookupByLibrary.simpleMessage("Account"),
"_Settings_appearanceTileSubtitle" : MessageLookupByLibrary.simpleMessage("Theme, font size"),
"_Settings_appearanceTileTitle" : MessageLookupByLibrary.simpleMessage("Appearance"),
"_Settings_brightnessTileOptionDark" : MessageLookupByLibrary.simpleMessage("Dark"),
"_Settings_brightnessTileOptionLight" : MessageLookupByLibrary.simpleMessage("Light"),
"_Settings_brightnessTileTitle" : MessageLookupByLibrary.simpleMessage("Brightness"),
"_Settings_editNameDescription" : MessageLookupByLibrary.simpleMessage("This is not your username. This is the name that will be visible to others."),
"_Settings_title" : MessageLookupByLibrary.simpleMessage("Settings"),
"_StartUsername_hostnameInvalidError" : MessageLookupByLibrary.simpleMessage("Invalid hostname"),
"_StartUsername_title" : MessageLookupByLibrary.simpleMessage("Enter username"),
"_StartUsername_unknownError" : MessageLookupByLibrary.simpleMessage("An unknown error occured"),
"_StartUsername_userIdInvalidError" : MessageLookupByLibrary.simpleMessage("Invalid user ID. Must be in the format of \'@name:server.tld\'"),
"_StartUsername_usernameInvalidError" : MessageLookupByLibrary.simpleMessage("Invalid username. May only contain letters, numbers, -, ., =, _ and /"),
"_StartUsername_wrongPasswordError" : MessageLookupByLibrary.simpleMessage("Wrong password. Please try again"),
"_Start_advanced" : MessageLookupByLibrary.simpleMessage("Advanced"),
"_Start_homeserver" : MessageLookupByLibrary.simpleMessage("Homeserver"),
"_Start_identityServer" : MessageLookupByLibrary.simpleMessage("Identity server"),
"_Start_login" : MessageLookupByLibrary.simpleMessage("Login"),
"_Start_loginWithEmail" : MessageLookupByLibrary.simpleMessage("Login with email"),
"_Start_loginWithPhone" : MessageLookupByLibrary.simpleMessage("Login with phone number"),
"_Start_loginWithUsername" : MessageLookupByLibrary.simpleMessage("Login with username"),
"_Start_register" : MessageLookupByLibrary.simpleMessage("Register"),
"_Time_today" : MessageLookupByLibrary.simpleMessage("Today"),
"_Time_yesterday" : MessageLookupByLibrary.simpleMessage("Yesterday"),
"appName" : MessageLookupByLibrary.simpleMessage("Pattle")
};
}
// Copyright (C) 2019 Wilko Manger
//
// This file is part of Pattle.
//
// Pattle is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Pattle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Pattle. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Strings l(BuildContext context) => AppLocalizations.of(context).strings;
class Strings {
const Strings();
// Common //
final appName = 'Pattle';
final name = 'Name';
final advanced = 'Advanced';
final username = 'Username';
final password = 'Password';
final confirm = 'Confirm';
final login = 'Login';
final register = 'Register';
final next = 'Next';
final homeserver = 'Homeserver';
final identityServer = 'Identity server';
final today = 'Today';
final yesterday = 'Yesterday';
final photo = 'Photo';
final typeAMessage = 'Type a message';
final you = 'You';
final andOthers = 'and others';
final newGroup = 'New group';
final groupName = 'Group name';
final participants = 'Participants';
final profile = 'Profile';
final settings = 'Settings';
final personal = 'Personal';
final public = 'Public';
final calls = 'Calls';
final description = 'Description';
final noDescriptionSet = 'No description has been set';
final cantSendMessages =
'You can\'t send messages to this group because you\'re no longer a participant.';
final connectionLost = 'Connection has been lost.\n'
'Make sure your phone has an active internet connection.';
final connectionFailed = 'Connection failed. '
'Check your internet connection';
final connectionFailedServerOverloaded = 'Connection failed. '
'The server is probably overloaded.';
final anErrorHasOccurred = 'An error has occurred:';
final thisErrorHasBeenReported =
'This error has been reported. Please restart Pattle.';
final youDeletedThisMessage = 'You deleted this message';
List<TextSpan> hasDeletedThisMessage(TextSpan name) =>
[name, TextSpan(text: ' deleted this message')];
// StartPage //
final loginWithPhone = 'Login with phone number';
final loginWithEmail = 'Login with email';
final loginWithUsername = 'Login with username';
// StartPage: UsernamePage //
final enterUsername = 'Enter username';
final ifYouDontHaveAnAccount =
"If you don't have an account, we'll create one";
final usernameInvalidError =
"Invalid username. May only contain letters, numbers, -, ., =, _ and /";
final userIdInvalidError =
"Invalid user ID. Must be in the format of '@name:server.tld'";
final hostnameInvalidError = 'Invalid hostname';
final unknownError = 'An unknown error occured';
final failedUsernameCheckAvailableError =
'Failed to check if the username is available';
final wouldYouLikeLoginOrRegister = 'Would you like to login or register?';
// StartPage: PasswordPage //
final enterPassword = 'Enter password';
final wrongPasswordError = 'Wrong password. Please try again';
String loggingInAs(String name) => 'Logging in as $name';
// SettingsPage //
final account = 'Account';
final accountDescription = 'Privacy, security, change password';
final appearance = 'Appearance';
final appearanceDescription = 'Theme, font size';
final brightness = 'Brightness';
final light = 'Light';
final dark = 'Dark';
final editNameDescription = 'This is not your username.'
' This is the name that will be visible to others.';
// Room creation event
List<TextSpan> createdThisGroup(TextSpan name) =>
[name, TextSpan(text: ' created this group')];
// Room upgrade event
List<TextSpan> upgradedThisGroup(TextSpan name) =>
[name, TextSpan(text: ' upgraded this group')];
// Topic change event
List<TextSpan> changedDescriptionTapToView(TextSpan name) => [
name,
TextSpan(text: ' changed the description of this group. Tap to view')
];
List<TextSpan> changedDescription(TextSpan name) =>
[name, TextSpan(text: ' changed the description of this group')];
// Member change events
List<TextSpan> changedTheirNameTo(TextSpan oldName, TextSpan newName) =>
[oldName, TextSpan(text: ' changed their name to '), newName];
List<TextSpan> joined(TextSpan name) => [name, TextSpan(text: ' joined')];
List<TextSpan> left(TextSpan name) => [name, TextSpan(text: ' left')];
List<TextSpan> wasBannedBy(TextSpan banee, TextSpan banner) =>
[banee, TextSpan(text: ' was banned by '), banner];
List<TextSpan> wasInvitedBy(TextSpan invitee, TextSpan inviter) =>
[invitee, TextSpan(text: ' was invited by '), inviter];
// Typing events
List<TextSpan> get typing => [TextSpan(text: 'typing...')];
List<TextSpan> isTyping(TextSpan name) => [
name,
TextSpan(text: ' is typing...'),
];