自动翻译.po文件?

34
以前有一些服务使用谷歌翻译API V1来自动翻译.po文件。 现在,谷歌已经停止了V1 API,并且在V2中,他们收取20美元来翻译100万个单词。
我搜索过但没有找到任何工具可以使用V2版本进行翻译。你会希望有人更新他们的工具,每20000个单词收取$2并赚取可观的利润。
是否有任何付费或免费的工具可以自动翻译.po文件?

1
根据Google的服务条款,它不允许您转售其付费翻译,因此没有人能够做出您所建议的事情。 - daveagp
"attranslate" 是一款现代化的工具,正好满足您的需求:https://github.com/fkirc/attranslate - Mike76
1
一个新的工具已经发布了,它非常适合自动翻译 .po 文件 https://www.ajexperience.com/po-translator/ - SuperAtic
4个回答

47

2
这太棒了!它或许可以节省90%的工作量! - daveagp
2
这不再是免费的了。收费也相当高昂。 - EHerman
5
它仍然是免费使用的,尽管界面很混乱。Google试图引导你使用翻译提供商这一事实并没有帮助。你可以点击“不需要”,将翻译文件标记为“完成”,然后下载自动翻译的文本。 - leepowers
1
天啊,这太棒了。 - Toby
3
谷歌曾经鼓励全球用户“建议”和免费为其翻译,如今他们已经积累了足够的资源,却开始向我们收费了!真是够酷的,不是吗? - Vassilis
显示剩余6条评论

2

太棒了!虽然在 %s 和 %d 变量方面存在一些 bug,但总体还是非常好的。谢谢。 - JohnWolf
当我计划编写自己的翻译器时,我来到这里(顺便说一下,是预先搜索引擎)并尝试了这个项目。截至2021年08月10日,它已经更新了8个月。我面临一个需要合并更改的缺陷,它与FUZZY记录有关。 - sunsetjunks

1

随意编辑此帖以进行更正或更好的错误处理。我不是关于.po文件格式的专家,但我认为它将满足我的angular-gettext需求。我想我唯一使用的外部库是Newtonsoft的Json.NET。我与Frengly没有任何关联。它在谷歌上出现了。如果您需要每3秒进行多个翻译,建议使用其他翻译API。


输入文件:template.pot

msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: Comment
msgid "You do not have permission to view this application"
msgstr ""

msgid "You have permission to view this application"
msgstr ""

输入命令:> program.exe template.pot en es_VE fr_FR


输出文件1: en.cache.json 这个文件的创建是为了避免重复使用翻译工具。

{
  "es_VE": {
    "You do not have permission to view this application": "Tu no la habana permiso que vista este aplicación",
    "You have permission to view this application": "Tu tienes permiso que vista este aplicación"
  },
  "fr_FR": {
    "You do not have permission to view this application": "Vous le faites pas as autorisation a vue cette une demande",
    "You have permission to view this application": "Tuas autorisation a vue cette une demande"
  }
}

输出文件2:es_VE.po

msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: Comment
msgid "You do not have permission to view this application"
msgstr "Tu no la habana permiso que vista este aplicación"

msgid "You have permission to view this application"
msgstr "Tu tienes permiso que vista este aplicación"

输出文件 3:fr_FR.po

msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: Comment
msgid "You do not have permission to view this application"
msgstr "Vous le faites pas as autorisation a vue cette une demande"

msgid "You have permission to view this application"
msgstr "Tuas autorisation a vue cette une demande"

源代码

public interface ICache
{
    void Add(string language, IEntry entry);

    IEntry Get(string language, string id);

    string GetSerialized();
}

public class JsonCache : ICache
{
    private Dictionary<string, Dictionary<string, string>> _cache;

    public JsonCache(string json)
    {
        this._cache = 
            json == null ?
            new Dictionary<string, Dictionary<string, string>>() :
            JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(json);
    }

    public void Add(string language, IEntry entry)
    {
        if (!this._cache.ContainsKey(language))
        {
            this._cache.Add(language, new Dictionary<string, string>());
        }

        var languageCache = this._cache[language];

        languageCache.Add(entry.Id, entry.Value);
    }

    public IEntry Get(string language, string id)
    {
        if (!this._cache.ContainsKey(language))
        {
            return null;
        }

        var languageCache = this._cache[language];

        Entry result = null;

        if (languageCache.ContainsKey(id))
        {
            result = new Entry();
            result.Id = id;
            result.Value =  languageCache[id];
        }

        return result;
    }

    public string GetSerialized()
    {
        return JsonConvert.SerializeObject(this._cache, Formatting.Indented);
    }
}

public interface IReader : IDisposable
{
    IEntry Read();
}

public class PoReader : IReader
{
    private StreamReader _reader;

    public PoReader(string fileName)
    {
        this._reader = new StreamReader(fileName);
    }

    public void Dispose()
    {
        if (this._reader != null)
        {
            this._reader.Dispose();
        }
    }

    public IEntry Read()
    {
        var entry = new Entry();

        while (entry.Id == null || entry.Value == null)
        {
            var line = this._reader.ReadLine();
            if (line == null)
            {
                return null;
            }

            if (line.StartsWith(Constants.StartComment))
            {
                entry.Comment = line.Substring(Constants.StartComment.Length);
            }
            else if (line.StartsWith(Constants.StartId))
            {
                entry.Id = line.Substring(Constants.StartId.Length);
                // Remove the double quotes.
                entry.Id = entry.Id.Substring(1, entry.Id.Length - 2);
            }
            else if (line.StartsWith(Constants.StartValue))
            {
                entry.Value = line.Substring(Constants.StartValue.Length);
                // Remove the double quotes.
                entry.Value = entry.Value.Substring(1, entry.Value.Length - 2);
            }
        }

        // Skip the first entry
        if (entry.Id.Length == 0)
        {
            return this.Read();
        }

        return entry;
    }
}

public class CachedTranslator : ITranslator
{
    private ITranslator _translator;
    private ICache _cache;

    public CachedTranslator(ICache cache, ITranslator translator)
    {
        this._translator = translator;
        this._cache = cache;
    }

    public IEntry Translate(string language, IEntry entry)
    {
        var result = this._cache.Get(language, entry.Id);
        if (result == null)
        {
            result = this._translator.Translate(language, entry);
            this._cache.Add(language, result);
        }
        else
        {
            // We don't want to use the cached comment.
            var clone = new Entry();

            clone.Comment = entry.Comment;
            clone.Value = result.Value;
            clone.Id = result.Id;

            result = clone;
        }
        return result;
    }
}

public class FrenglyTranslator : ITranslator
{
    private string _password;
    private string _email;
    private string _inLanguage;

    public FrenglyTranslator(string email, string password, string inLanguage)
    {
        this._email = email;
        this._password = password;
        this._inLanguage = inLanguage;
    }

    public IEntry Translate(string language, IEntry entry)
    {
        var url = string.Format("http://syslang.com?src={4}&dest={0}&text={1}&email={2}&password={3}&outformat=json",
            language.Substring(0, 2),
            entry.Id,
            this._email,
            this._password,
            this._inLanguage);

        var result = new Entry();
        result.Id = entry.Id;
        result.Comment = entry.Comment;

        using (var client = new HttpClient())
        {
            var clientResult = client.GetStringAsync(url).Result;
            var jo = (JObject)JsonConvert.DeserializeObject(clientResult);               
            result.Value = jo.Property("translation").Value.Value<string>();
        }

        // Must wait 3 seconds between calls.
        Thread.Sleep(3001);

        return result;
    }
}

public interface ITranslator
{
    IEntry Translate(string language, IEntry entry);
}

public interface IWriter : IDisposable
{
    void Write(IEntry entry);
}

public class PoWriter : IWriter
{
    private StreamWriter _writer;

    public PoWriter(string fileName)
    {
        this._writer = new StreamWriter(fileName);

        var header = @"msgid """"
msgstr """"
""Content-Type: text/plain; charset=UTF-8\n""
""Content-Transfer-Encoding: 8bit\n""";

        this._writer.WriteLine(header);
    }

    public void Write(IEntry entry)
    {
        this._writer.WriteLine();

        if (entry.Comment != null && entry.Comment.Length > 0)
        {
            this._writer.WriteLine(Constants.StartComment + entry.Comment);
        }

        this._writer.WriteLine(string.Format("{0}\"{1}\"", Constants.StartId, entry.Id));
        this._writer.WriteLine(string.Format("{0}\"{1}\"", Constants.StartValue, entry.Value));
    }

    public void Dispose()
    {
        if (this._writer != null)
        {
            this._writer.Dispose();
        }
    }
}

public static class Constants
{
    public const string StartComment = "#: ";
    public const string StartId = "msgid ";
    public const string StartValue = "msgstr ";
}

public class Entry : IEntry
{
    public string Comment { get; set; }

    public string Value { get; set; }

    public string Id { get; set; }
}

public interface IEntry
{
    string Comment { get; }

    string Value { get; }

    string Id { get; }
}

class Program
{
    private const string cacheFileNameSuffix = ".cache.json";
    private const string frenglyEmail = "your.em@il.com";
    private const string frenglyPassword = "YourPassword";

    static void Main(string[] args)
    {
        //
        // INITIALIZE
        //
        var inFileName = args[0];
        var inLanguage = args[1];
        var outLanguages = args.Skip(2);

        // ICache
        var cacheFileName = inLanguage + cacheFileNameSuffix;
        var json = File.Exists(cacheFileName) ? File.ReadAllText(cacheFileName) : null;
        ICache cache = new JsonCache(json);

        // ITranslator
        ITranslator translator = new FrenglyTranslator(frenglyEmail, frenglyPassword, inLanguage);
        ITranslator cachedTranslator = new CachedTranslator(cache, translator);

        // IWriters
        var writers = new Dictionary<string, IWriter>();
        foreach (var language in outLanguages)
        {
            writers.Add(language, new PoWriter(language + ".po"));
        }

        try
        {
            using (IReader reader = new PoReader(inFileName))
            {
                //
                // RUN
                //
                IEntry entry = null;
                while (true)
                {
                    entry = reader.Read();
                    if (entry == null)
                    {
                        break;
                    }

                    foreach (var kv in writers)
                    {
                        var translated = cachedTranslator.Translate(kv.Key, entry);
                        kv.Value.Write(translated);
                    }
                }
            }
        }
        finally
        {
            // Store the cache.
            File.WriteAllText(cacheFileName, cache.GetSerialized());

            //
            // CLEANUP
            //

            // Dispose of the writers.
            foreach (var writer in writers.Values)
            {
                if (writer != null)
                {
                    writer.Dispose();
                }
            }
        }
    }
}

-3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接