65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 HTTPModules 和机器翻译 (MT) 自动翻译站点

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (9投票s)

2009年1月26日

CPOL

6分钟阅读

viewsIcon

38990

downloadIcon

504

使用 HTTP 模块和机器翻译实现网站自动翻译。

引言

我们大约有 20 个网站。管理层正在向海外扩张,并希望所有网站都能多语言化,最好是昨天就完成。而且,棘手的是,有些内容是由用户生成的。面对永无止境的资源文件标记的未来,我需要找到一个更好的解决方案。作为一名程序员,我想创造一些可以解决我所有问题的东西。我需要一种可以用于任何网站、并且不需要太多修改源代码的方案。它还需要能够自行翻译页面,但同时又需要学习用户更新内容时产生的生词。

老师

这些网站碰巧位于防火墙后面,因此使用 Google 或 BabelFish 的翻译服务实际上是行不通的。但是,使用机器翻译听起来是个好主意(总比处理所有那些资源文件要好)。机器翻译的效果相当不错,但并非一门精确的科学,因此我们需要一种方法来修改它的翻译结果。我找到一家名为 SysTran 的公司,他们提供 API。这样,我就有了一个“老师”,可以用来学习新语言的页面。大多数机器翻译程序都有一个函数,你可以传递一个 URL 给它,它就会一次性翻译整个页面。问题是它太慢了,而且经常会搞乱你的 JavaScript 或页面上的其他东西。这种方法对我来说太笨重了。我需要一种方法来精确地修改内容:标签、组合框数据、列表数据等等……

通用化

我决定不想为我们使用的每个控件编写新的控件或包装器。我不想修改网站上的代码。我们在 ASP.NET 中做的所有这些最终都会在某个时候被转换成纯粹的 HTML,而 HTML 中需要处理的控件要少得多。所以,如果我在 ASP.NET 生成 HTML 后,在它发送给客户端之前进行翻译,我就可以在不搞乱 JavaScript 的情况下做到这一点,只翻译我想要翻译的内容。HTTP 模块非常适合这个任务。我不仅可以翻译原始 HTML,还可以在任何网站的前端添加它,而不会破坏网站的内部运作。

就喜欢原始的

实际上,获取原始输出有点棘手。唯一能获取它的地方是创建一个 Filter 对象并将其分配给 Response 对象,如下所示……

Dim f = _Context.Response.Filter
Dim sr As TranslateFilterToDom = New TranslateFilterToDom(f)
sr.Language = GetUserLanguage()
_Context.Response.Filter = sr

你创建一个继承自 Stream 的对象。大部分内容你都可以保持不变,只需填充必要的部分。不变的是 Write 方法。这是一个流,所以 HTML 是分块填充的。我们寻找的是结束 HTML 标签……

Public Overrides Sub Write(ByVal Buffer() As Byte, _
       ByVal offset As Integer, ByVal count As Integer)
    Dim sBuffer As String = System.Text.UTF8Encoding.UTF8.GetString(Buffer, offset, count)
    Dim rHTML As Regex = New Regex("</html>", RegexOptions.IgnoreCase)
    If (Not rHTML.IsMatch(sBuffer)) Then
        responseHtml.Append(sBuffer)
    Else
        responseHtml.Append(sBuffer)
        Dim finalHtml As String = responseHtml.ToString()
        ' Transform the response and write it back out
        If Language <> "en" Then ' Don't do anything if its English
            xml = New XmlDocument 'Create a place to put the XHTML
            Dim xmldecl As XmlDeclaration
            xmldecl = xml.CreateXmlDeclaration("1.0", "UTF-8", Nothing)
            finalHtml = finalHtml.Substring(finalHtml.ToLower.IndexOf("<html"))
        'Help out the HTML a bit to make it XHTML
            finalHtml = finalHtml.Replace(" ", " ")
            finalHtml = finalHtml.Replace("<br>", "<br/>")
            finalHtml = finalHtml.Replace("<hr>", "<hr/>")
            finalHtml = finalHtml.Replace("&", "&")
            finalHtml = finalHtml.Replace("<head>", "<head>" & META) 
        'Add a META tag to make sure its UTF-8
            xml.LoadXml(finalHtml)
            xml.InsertBefore(xmldecl, xml.DocumentElement)
            finalHtml = Translate(xml) 'Start the Translation
            finalHtml = finalHtml.Replace("&", "&") 'Put the & back in
        End If
        Dim data As Byte() = System.Text.UTF8Encoding.UTF8.GetBytes(finalHtml)
        _sink.Write(data, 0, data.Length)
    End If
End Sub

这里的想法是在 HTML 流向客户端的过程中解析 HTML 标签。这样,我们就无需担心服务器控件,也无需更改应用程序中的代码。问题是如何进行解析。如果 HTML 符合 XHTML 标准,我们可以直接将其加载到 XMLDocument 中并进行解析。然后,我们将其传递给 Translate 方法……

Private Function Translate(ByVal poxml As XmlDocument) As String
    Dim loNode As XmlNode = DirectCast(poxml.DocumentElement, XmlNode)
    TranslateNode(poxml.DocumentElement)
    If _IsManaul Then  
    'The manual switch will put the script on the page to allow for user translation.
        Dim xmlNode As XmlNode = poxml.GetElementsByTagName("head")(0)
        Dim xmlScript As XmlElement = poxml.CreateElement("Script")
        xmlScript.InnerXml() = GetManualScript()
        xmlNode.AppendChild(xmlScript)
    End If
    Return poxml.OuterXml
End Function 

所以,我们在这里启动递归来解析 XMLDocument。此外,我们将处理 MT 程序无法翻译用户所选语言的情况,这正是 IsManual(我知道代码中拼写错了)的用途。

所以,这是外科手术式处理的部分。我们需要获取用户可以看到的文本。我们关心 HTML 标签之间的文本。我们关心 value 属性中的文本。我们不关心文本框或文本区域,因为用户将在其中输入内容。我们不关心 scriptstyle 标签中的内容。

Private Sub TranslateNode(ByVal poNode As XmlNode)
'Find where the text we want to translate is
    Dim lbTraverse As Boolean = True
    Select Case poNode.Name.ToLower 
        Case "#text" 'catches text between tags
            poNode.Value = TranslateText(poNode.Value, Nothing)
        Case "input"
            Dim lsType As String
            If poNode.Attributes.ItemOf("type") Is Nothing Then
                lsType = "text"
            Else
                lsType = poNode.Attributes.ItemOf("type").Value.ToLower
            End If
            If Not poNode.Attributes.ItemOf("value") Is Nothing Then
                Select Case lsType 
                    Case "button", "submit"
                    poNode.Attributes.ItemOf("value").Value = _
                         TranslateText(poNode.Attributes.ItemOf("value").Value, poNode 
                End Select 
            End If
        Case "script", "style", "textarea" 'don't care about anything in here.
            lbTraverse = False 
    End Select
    If lbTraverse Then 'Go on down the the recursion
        For Each loNode As XmlNode In poNode.ChildNodes 
            TranslateNode(loNode)
        Next
    End If
End Sub

那么,既然我们已经获得了需要翻译的文本,我们如何翻译它呢?使用 MT 的缺点是它速度慢,至少比从数据库中查找单词要慢。我们的做法是,一旦我们翻译了一个单词或短语,我们就会将其保存起来,供我们的网站或任何使用此 HTTP 模块的网站使用。

Private Function TranslateText(ByVal psString As String, _
       ByVal pnode As XmlNode) As String 

    Dim result As String = String.Empty
    'clean the text up so we can translate it
    psString = psString.Replace(vbCr, "")
    psString = psString.Replace(vbCrLf, "")
    psString = psString.Replace(vbLf, "")
    psString = psString.Replace(vbTab, "")
    psString = psString.Trim
    result = Lookup(psString) 'Try looking it up out of the database
    If result.Trim = String.Empty Then 'If its not there then translate it
        Dim loTranslator As ITranslator = New Systran 'Set up your own translator interface
        'Make sure you have a translator and that translator will translate the users language
        If Not loTranslator Is Nothing AndAlso _  
                Array.Exists(loTranslator.AvailableLanguages, AddressOf HasLanguage) Then
            result = loTranslator.Translate(psString, "en", Language) 'Get the word
            SaveWord(psString, result) 'And save it off so you don't ever have
            'to go get it again
        Else  
        'If you don't support the language or don't have a translator, 
        'set the Manual switch so users can translate it themselves
            _IsManaul = True
            result = psString
        End If
    End If
    Return result
End Function

魔术就发生在这里。我设置了它,以便你只需实现 ITranslator 即可添加自己的翻译器。我们正在考虑使用 Systran,它有一个 Web 服务 API。

Public Function Translate(ByVal psString As String, _
        ByVal psFromLanguage As String, ByVal psToLanguage As String) _
        As String Implements ITranslator.Translate

    Dim lsURL As String = My.Settings.TransaltionURL.Trim.Replace("@Language", psToLanguage)
    Dim loWebclient As WebClient = New WebClient
    loWebclient.Encoding = Encoding.UTF8 
    'Make sure you tell the web client to be unicode compliant
    Dim result As String = loWebclient.UploadString(lsURL, psString).Trim
    Return result.Replace("body=", "") 'Got to get rid of this
End Function

那么,如果……

如果你没有 MT 服务器,或者用户使用的语言 MT 无法翻译怎么办?你需要一种方法让用户为你翻译页面。这是网站翻译的 Wikipedia 方法。通过创建一个列表中常用短语和网页控件中使用的元素,你可以通过使用在线翻译器之一来翻译所有内容,或者让用户填写翻译。这足以处理静态内容。然后,还有用户生成的内容。我们需要一种方法让用户选择以英语显示的内容并为我们翻译它。所以,如果手动翻译开关打开,我们就会在 HTML 中加载必要的 JavaScript,让用户选择内容,并在鼠标抬起时,弹出一个对话框,允许他们提供建议。在我们的网站上,我们捕获用户的登录信息,因为我们是单点登录,所以如果他们输入任何不当内容,我们可以对其进行处理。

en.jpg

这是演示的英文版本。不进行翻译。

jp.jpg

使用 Systran MT 已翻译成日语。

Manual.jpg

请原谅拼写错误。Systran 不翻译越南语。所以,用户需要提供翻译。他们只需选择英文文本,然后会弹出一个对话框询问翻译。然后,该翻译将被存储在数据库中供所有网站使用。

db.jpg

这是数据库表。对于任何大小的网站或多个网站,它都会变得相当大,但所有网站都可以使用它。一旦一个网站学会了一个单词,所有网站在那个时候都知道了,并且不需要再查找了。

Systran 不翻译越南语。所以,用户需要提供翻译。他们只需选择英文文本,然后会弹出一个对话框询问翻译。然后,该翻译将被存储在数据库中供所有网站使用。当然,如果用户拼写错误,正如这里所示,它会显示拼写错误,因此添加一个多语言拼写检查器会很有帮助。

结论

抛弃 RESX 文件,让你的网站通过 MT 作为老师相互学习。我希望这是一个你可以扩展的想法。代码可能需要一些调整才能集成到你的网站中,但希望这是一个你可以使用的想法。

© . All rights reserved.