Merge pull request #2736 from pontaoski/work/janb/i18n

Add i18n system
This commit is contained in:
Lucas Nicodemus 2022-10-20 01:25:33 -07:00 committed by GitHub
commit fda1de52a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 340 additions and 17 deletions

71
.github/scripts/check-diff.py vendored Executable file
View file

@ -0,0 +1,71 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2019 Corentin Noël <tintou@noel.tf>
#
# SPDX-License-Identifier: GPL-3.0-only
# Taken from https://github.com/elementary/actions/blob/master/gettext-template/check-diff.py
import git
import io
def commit_to_repo(repo):
print('There are translation changes, committing changes to repository!')
files = repo.git.diff(None, name_only=True)
for f in files.split('\n'):
if f.endswith ('.po') or f.endswith ('.pot'):
repo.git.add(f)
repo.git.commit('-m', 'Update translation template')
infos = repo.remotes.origin.push()
has_error=False
error_msg=''
for info in infos:
if info.flags & git.remote.PushInfo.ERROR == git.remote.PushInfo.ERROR:
has_error=True
error_msg += info.summary
if has_error:
raise NameError('Unable to push to repository: ' + error_msg)
print('Checking the repository for new translations...')
repo = git.Repo('.')
t = repo.head.commit.tree
files = repo.git.diff(None, name_only=True)
needs_commit=False
for f in files.split('\n'):
if f.endswith ('.pot'):
raw_diff = repo.git.diff(t, f)
output = io.StringIO()
for line in raw_diff.splitlines():
if line.startswith ('+++'):
continue
if line.startswith ('---'):
continue
if line.startswith ('diff'):
continue
if line.startswith ('index'):
continue
if line.startswith ('@@'):
continue
if line.startswith (' '):
continue
if line.startswith ('+#:'):
continue
if line.startswith ('-#:'):
continue
if line.startswith ('-"'):
continue
if line.startswith ('+"'):
continue
if not line.strip():
continue
print(line, file=output)
if output.getvalue().strip():
print(f + " has changed!")
needs_commit = True
output.close()
if needs_commit:
commit_to_repo(repo)
else:
print('The translations are up-to-date!')

62
.github/scripts/i18n.sh vendored Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2019 Corentin Noël <tintou@noel.tf>
# SPDX-FileCopyrightText: 2022 Janet Blackquill <uhhadd@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-only
# adapted from https://github.com/elementary/actions/blob/master/gettext-template/entrypoint.sh
set -e
export DEBIAN_FRONTEND="noninteractive"
# if a custom token is provided, use it instead of the default github token.
if [ -n "$GIT_USER_TOKEN" ]; then
GITHUB_TOKEN="$GIT_USER_TOKEN"
fi
if [ -z "${GITHUB_TOKEN}" ]; then
echo "\033[0;31mERROR: The GITHUB_TOKEN environment variable is not defined.\033[0m" && exit 1
fi
# Git repository is owned by another user, mark it as safe
git config --global --add safe.directory /github/workspace
# get default branch, see: https://davidwalsh.name/get-default-branch-name
DEFAULT_BRANCH="$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)"
if [ -z "${INPUT_TRANSLATION_BRANCH}" ]; then
TRANSLATION_BRANCH="${DEFAULT_BRANCH}"
else
TRANSLATION_BRANCH="${INPUT_TRANSLATION_BRANCH}"
fi
# default email and username to github actions user
if [ -z "$GIT_USER_EMAIL" ]; then
GIT_USER_EMAIL="action@github.com"
fi
if [ -z "$GIT_USER_NAME" ]; then
GIT_USER_NAME="GitHub Action"
fi
# make sure branches are up-to-date
git fetch
echo "Setting up git credentials..."
git remote set-url origin https://x-access-token:"$GITHUB_TOKEN"@github.com/"$GITHUB_REPOSITORY".git
git config --global user.email "$GIT_USER_EMAIL"
git config --global user.name "$GIT_USER_NAME"
echo "Git credentials configured."
# get the project's name:
PROJECT="$(basename "$GITHUB_REPOSITORY")"
echo "Project: $PROJECT"
sudo apt-get -qq update
sudo apt-get -qq install python3-git
dotnet tool install --global GetText.NET.Extractor --version 1.6.6
GetText.Extractor --order -s TShock.sln -t i18n/template.pot
python3 .github/scripts/check-diff.py

View file

@ -33,6 +33,9 @@ jobs:
with:
dotnet-version: '6.0.100'
- name: Install msgfmt
run: sudo apt-get install -y gettext
- name: Produce build
run: |
cd TShockLauncher

23
.github/workflows/i18n-extract.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: i18n extraction
on:
push:
branches: [ general-devel ]
jobs:
extract:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- uses: actions/setup-dotnet@v3
with:
dotnet-version: |
3.1.x
6.0.100
- name: Run i18n checking/extraction script
run: ./.github/scripts/i18n.sh
shell: bash

View file

@ -649,7 +649,7 @@ namespace TShockAPI
string cmdName;
if (index == 0) // Space after the command specifier should not be supported
{
player.SendErrorMessage("Invalid command entered. Type {0}help for a list of valid commands.", Specifier);
player.SendErrorMessage(I18n.C.GetString("Invalid command entered. Type {0}help for a list of valid commands.", Specifier));
return true;
}
else if (index < 0)
@ -677,28 +677,28 @@ namespace TShockAPI
call(new CommandArgs(cmdText, player, args));
return true;
}
player.SendErrorMessage("Invalid command entered. Type {0}help for a list of valid commands.", Specifier);
player.SendErrorMessage(I18n.C.GetString("Invalid command entered. Type {0}help for a list of valid commands.", Specifier));
return true;
}
foreach (Command cmd in cmds)
{
if (!cmd.CanRun(player))
{
TShock.Utils.SendLogs(string.Format("{0} tried to execute {1}{2}.", player.Name, Specifier, cmdText), Color.PaleVioletRed, player);
player.SendErrorMessage("You do not have access to this command.");
TShock.Utils.SendLogs(I18n.C.GetString("{0} tried to execute {1}{2}.", player.Name, Specifier, cmdText), Color.PaleVioletRed, player);
player.SendErrorMessage(I18n.C.GetString("You do not have access to this command."));
if (player.HasPermission(Permissions.su))
{
player.SendInfoMessage("You can use '{0}sudo {0}{1}' to override this check.", Specifier, cmdText);
player.SendInfoMessage(I18n.C.GetString("You can use '{0}sudo {0}{1}' to override this check.", Specifier, cmdText));
}
}
else if (!cmd.AllowServer && !player.RealPlayer)
{
player.SendErrorMessage("You must use this command in-game.");
player.SendErrorMessage(I18n.C.GetString("You must use this command in-game."));
}
else
{
if (cmd.DoLog)
TShock.Utils.SendLogs(string.Format("{0} executed: {1}{2}.", player.Name, silent ? SilentSpecifier : Specifier, cmdText), Color.PaleVioletRed, player);
TShock.Utils.SendLogs(I18n.C.GetString("{0} executed: {1}{2}.", player.Name, silent ? SilentSpecifier : Specifier, cmdText), Color.PaleVioletRed, player);
cmd.Run(cmdText, silent, player, args);
}
}
@ -1011,7 +1011,7 @@ namespace TShockAPI
{
args.Player.SendSuccessMessage("Account \"{0}\" has been registered.", account.Name);
args.Player.SendSuccessMessage("Your password is {0}.", echoPassword);
if (!TShock.Config.Settings.DisableUUIDLogin)
args.Player.SendMessage($"Type {Specifier}login to sign in to your account using your UUID.", Color.White);
@ -1019,7 +1019,7 @@ namespace TShockAPI
args.Player.SendMessage($"Type {Specifier}login \"{account.Name.Color(Utils.GreenHighlight)}\" {echoPassword.Color(Utils.BoldHighlight)} to sign in to your account.", Color.White);
else
args.Player.SendMessage($"Type {Specifier}login {echoPassword.Color(Utils.BoldHighlight)} to sign in to your account.", Color.White);
TShock.UserAccounts.AddUserAccount(account);
TShock.Log.ConsoleInfo("{0} registered an account: \"{1}\".", args.Player.Name, account.Name);
}
@ -1145,7 +1145,7 @@ namespace TShockAPI
TShock.UserAccounts.SetUserGroup(account, args.Parameters[2]);
TShock.Log.ConsoleInfo(args.Player.Name + " changed account " + account.Name + " to group " + args.Parameters[2] + ".");
args.Player.SendSuccessMessage("Account " + account.Name + " has been changed to group " + args.Parameters[2] + "!");
//send message to player with matching account name
var player = TShock.Players.FirstOrDefault(p => p != null && p.Account?.Name == account.Name);
if (player != null && !args.Silent)
@ -1403,7 +1403,7 @@ namespace TShockAPI
LineTextColor = Color.White
});
break;
case "examples":
args.Player.SendMessage("", Color.White);
args.Player.SendMessage("Ban Usage Examples", Color.White);
@ -1562,7 +1562,7 @@ namespace TShockAPI
if (banUuid)
{
banResult = DoBan($"{Identifier.UUID}{player.UUID}", reason, expiration);
banResult = DoBan($"{Identifier.UUID}{player.UUID}", reason, expiration);
}
if (banName)
@ -1625,7 +1625,7 @@ namespace TShockAPI
args.Player.SendMessage($"Invalid Ban List syntax. Refer to {"ban help list".Color(Utils.BoldHighlight)} for details on how to use the {"ban list".Color(Utils.BoldHighlight)} command", Color.White);
return;
}
var bans = from ban in TShock.Bans.Bans
where ban.Value.ExpirationDateTime > DateTime.UtcNow
orderby ban.Value.ExpirationDateTime ascending
@ -1664,7 +1664,7 @@ namespace TShockAPI
DisplayBanDetails(ban);
}
string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower();
switch (subcmd)
{
@ -2698,7 +2698,7 @@ namespace TShockAPI
TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY);
spawnName = "a Solar Pillar";
break;
case "nebula pillar":
case "nebula pillar":
npc.SetDefaults(507);
TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY);
spawnName = "a Nebula Pillar";
@ -5827,7 +5827,7 @@ namespace TShockAPI
target.SendErrorMessage($"{user.Name} just killed you!");
}
}
private static void Respawn(CommandArgs args)
{
if (!args.Player.RealPlayer && args.Parameters.Count == 0)
@ -5857,7 +5857,7 @@ namespace TShockAPI
}
playerToRespawn = players[0];
}
else
else
playerToRespawn = args.Player;
if (!playerToRespawn.Dead)

46
TShockAPI/I18n.cs Normal file
View file

@ -0,0 +1,46 @@
/*
TShock, a server mod for Terraria
Copyright (C) 2022 Janet Blackquill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Globalization;
using System.IO;
using GetText;
namespace TShockAPI
{
static class I18n {
static string TranslationsDirectory => Path.Combine(AppContext.BaseDirectory, "i18n");
static CultureInfo TranslationCultureInfo
{
get
{
// cross-platform mapping of cultureinfos can be a bit screwy, so give our users
// the chance to explicitly spell out which translation they would like to use.
// this is an environment variable instead of a flag because this needs to be
// valid whether the passed flags are in a sane state or not.
if (Environment.GetEnvironmentVariable("TSHOCK_LANGUAGE") is string overrideLang)
{
return new CultureInfo(overrideLang);
}
return CultureInfo.CurrentUICulture;
}
}
/// <value>Instance of a <c>GetText.Catalog</c> loaded with TShockAPI translations for user's specified language</value>
public static Catalog C = new Catalog("TShockAPI", TranslationsDirectory, TranslationCultureInfo);
}
}

View file

@ -33,6 +33,7 @@
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="GetText.NET" Version="1.6.6" />
<PackageReference Include="MySql.Data" Version="8.0.31" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.10" />
</ItemGroup>

View file

@ -24,6 +24,7 @@
<PackageReference Include="MySql.Data" Version="8.0.31" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.10" />
<PackageReference Include="ModFramework" Version="1.1.6" GeneratePathProperty="true" /> <!-- only used to extract out to ./bin. -->
<PackageReference Include="GetText.NET" Version="1.6.6" /> <!-- only used to extract out to ./bin. -->
<!-- the launcher doesnt need the direct OTAPI reference, but since PackageReference[ExcludeFromSingleFile] doesnt work, exclude the assets and copy manually -->
<PackageReference Include="OTAPI.Upcoming" Version="3.1.14" ExcludeAssets="all" GeneratePathProperty="true" />
@ -41,6 +42,31 @@
</None>
</ItemGroup>
<Target Name="CheckMsgfmtCallable">
<Exec Command="msgfmt --help > NUL" IgnoreExitCode="True" Condition=" '$(OS)' == 'Windows_NT' ">
<Output TaskParameter="ExitCode" PropertyName="MsgfmtExitCode" />
</Exec>
<Exec Command="msgfmt --help 2>/dev/null >/dev/null" IgnoreExitCode="True" Condition=" '$(OS)' != 'Windows_NT' ">
<Output TaskParameter="ExitCode" PropertyName="MsgfmtExitCode" />
</Exec>
</Target>
<!-- The condition for a Target can't come from the values of another target, so instead we have to put the same condition on all three of the items inside. -->
<Target
Name="GenerateMOFiles"
DependsOnTargets="CheckMsgfmtCallable"
AfterTargets="PostBuildEvent;Publish"
Inputs="..\i18n\**\*.po"
Outputs="$(OutDir)i18n\**\*.mo">
<ItemGroup Condition="'$(MsgfmtExitCode)' == '0'">
<POFiles Include="..\i18n\**\*.po" />
</ItemGroup>
<MakeDir Directories="$(OutDir)i18n/%(POFiles.RecursiveDir)" Condition="'$(MsgfmtExitCode)' == '0'" />
<Exec Command="msgfmt -o $(OutDir)i18n/%(RecursiveDir)%(Filename).mo @(POFiles)" Outputs="$(OutDir)i18n\**\*.mo" Condition="'$(MsgfmtExitCode)' == '0'">
<Output ItemName="Generated" TaskParameter="Outputs"/>
</Exec>
</Target>
<Target Name="CreateServerPlugins" AfterTargets="PostBuildEvent;Publish">
<MakeDir Directories="$(OutDir)ServerPlugins" />
<MakeDir Directories="$(PublishDir)ServerPlugins" />
@ -50,6 +76,12 @@
<Copy SourceFiles="@(ApiFiles)" DestinationFolder="$(OutDir)ServerPlugins" ContinueOnError="true" />
<Copy SourceFiles="@(ApiFiles)" DestinationFolder="$(PublishDir)ServerPlugins" ContinueOnError="true" />
</Target>
<Target Name="CopyI18n" AfterTargets="Publish">
<ItemGroup>
<MOFiles Include="$(OutDir)**/*.mo"/>
</ItemGroup>
<Copy SourceFiles="@(MOFiles)" DestinationFolder="$(PublishDir)%(RecursiveDir)" />
</Target>
<Target Name="MoveBin" AfterTargets="Publish">
<ItemGroup>
<MoveBinaries Include="$(PublishDir)*" Exclude="$(PublishDir)\TShock.Server*" />

0
i18n/.gitkeep Normal file
View file

41
i18n/template.pot Normal file
View file

@ -0,0 +1,41 @@
msgid ""
msgstr ""
"Project-Id-Version: TShock\n"
"POT-Creation-Date: 2022-10-20 00:16:51-0400\n"
"PO-Revision-Date: 2022-10-20 00:16:51-0400\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: GetText.NET Extractor\n"
#: ../../TShockAPI/Commands.cs:701
#, csharp-format
msgid "{0} executed: {1}{2}."
msgstr ""
#: ../../TShockAPI/Commands.cs:687
#, csharp-format
msgid "{0} tried to execute {1}{2}."
msgstr ""
#: ../../TShockAPI/Commands.cs:652
#: ../../TShockAPI/Commands.cs:680
#, csharp-format
msgid "Invalid command entered. Type {0}help for a list of valid commands."
msgstr ""
#: ../../TShockAPI/Commands.cs:691
#, csharp-format
msgid "You can use '{0}sudo {0}{1}' to override this check."
msgstr ""
#: ../../TShockAPI/Commands.cs:688
msgid "You do not have access to this command."
msgstr ""
#: ../../TShockAPI/Commands.cs:696
msgid "You must use this command in-game."
msgstr ""

44
i18n/tok/TShockAPI.po Normal file
View file

@ -0,0 +1,44 @@
# Janet Blackquill <uhhadd@gmail.com>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: TShock\n"
"POT-Creation-Date: 2022-10-20 00:16:51-0400\n"
"PO-Revision-Date: 2022-10-19 23:20-0400\n"
"Last-Translator: Janet Blackquill <uhhadd@gmail.com>\n"
"Language-Team: none>\n"
"Language: tok\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || n%10>=5 && n%10<=9 || "
"n%100>=11 && n%100<=14 ? 2 : 3;\n"
"X-Generator: Lokalize 22.11.70\n"
#: ../../TShockAPI/Commands.cs:701
#, csharp-format
msgid "{0} executed: {1}{2}."
msgstr "jan {0} li kepeken: {1}{2}."
#: ../../TShockAPI/Commands.cs:687
#, csharp-format
msgid "{0} tried to execute {1}{2}."
msgstr "jan {0} li wile kepeken {1}{2}. taso ona li ken ala."
#: ../../TShockAPI/Commands.cs:652 ../../TShockAPI/Commands.cs:680
#, csharp-format
msgid "Invalid command entered. Type {0}help for a list of valid commands."
msgstr "ilo ni li lon ala. sina wile sona e ilo lon la o toki e {0}help."
#: ../../TShockAPI/Commands.cs:691
#, csharp-format
msgid "You can use '{0}sudo {0}{1}' to override this check."
msgstr "sina wile weka e awen pona ni la o kepeken '{0}sudo {0}{1}'."
#: ../../TShockAPI/Commands.cs:688
msgid "You do not have access to this command."
msgstr "sina ken ala kepeken ilo ni."
#: ../../TShockAPI/Commands.cs:696
msgid "You must use this command in-game."
msgstr "ilo ni li pali lon musi taso."