Språkhantering
Har en webbapplikation som ska stödja flera språk.
Tycker inte resource hanteringen känns bra, skulle man kunna ha språkstödet i constant class?
Eller andra tips?
Svara
Sv: Språkhantering
Hej, jag byggde en liknande sådan funktion för pellesoft och faktiskt var det urtypen för språkinställning för EPiServer när den var i sin tidigare version.
Exempel: språkhantering vb.net
Många pratar om språkhantering och för att få till en sådan på ett bra sätt tänkte jag helt enkelt skriva en artikel för att hjälpa dig på traven. Det finns inbyggt i .net men jag tyckte att det var enklare att ha en extern xml-fil som jag kunde skicka upp till servern i tid och otid för att snabbt kunna göra ändringar.
Vi börjar helt enkelt med att skapa oss en xml-fil som till en början inte blir speciellt märkvärdig. Denna kan sedan byggas på mer och mer, med fler språk och du kan till och med separera dessa så en xml-fil tillhör respektive språk. I mitt syfte kör vi i alla fall två språk, men vi har allt data samlat på en och samma sida.
1 2 3 4 5 6 7 8 9 10 | <? xml version = "1.0" encoding = "utf-8" ?> < languages > < language name = "Svenska" id = "SV" > </ language > < language name = "Engelska" id = "EN" > </ language > </ languages > |
Nu har vi vår första xml-fil som du har skapat i notepad eller någon annan editor. Som du ser här så har vi starttaggen languages, där efter kommer language-taggen för svenska och därefter engelska. Som du förstår nu så kan du alltså bygga denna fil hierarkiskt hur djup som helst för att gruppera din information. Vi börjar med att fylla på några små objekt för såväl svenska som engelska och därefter skall vi plocka ut informationen och använda den i vår applikation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <? xml version = "1.0" encoding = "utf-8" ?> < languages > < language name = "Svenska" id = "SV" > < page > < language >In English</ language > </ page > </ language > < language name = "Engelska" id = "EN" > < page > < language >På Svenska</ language > </ page > </ language > </ languages > |
Nu har vi lagt en rubrik som vi kallar page, och under page några objekt - typiska menyalternativ i toppen på pellesoft som du ser. Nu är det dags att bygga en funktion som hjälper oss att växla mellan språk. Enklast är att använda sig av ett sessionsobjekt och det börjar vi med genom att ha en link som vi trycker på om vart annat för att växla språket.
1 | <asp:linkbutton id= "lnkLanguage" runat= "server" >LinkButton</asp:linkbutton> |
När vi klickar på denna länken så har vi en funktion kopplad till den i codebehind som ser ut som följer:
1 2 3 4 5 6 7 8 9 | Private Sub lnkLanguage_Click( ByVal sender As System. Object , ByVal e As System.EventArgs) Handles lnkLanguage.Click If Session( "UserCurrentLanguage" ) = Language.English Then Session( "UserCurrentLanguage" ) = Language.Swedish Else Session( "UserCurrentLanguage" ) = Language.English End If Response.Redirect(Request.Url.ToString, True ) End Sub |
Först tittar vi lite på response.redirect här. Den används för att ladda om hela sidan igen då koden troligast ligger i en usercontrol som visar menyn. Genom att ladda om sidan kommer informationen att presenteras igen men eftersom vi bytt språk ser man även ändringarna. Det står Language.English och .Swedish. Dessa har jag lagt i min basklass för att enkelt nå den. Du kan göra samma sak genom att lägga till följande.
1 2 3 4 | Public Enum Language Swedish = 1 English = 2 End Enum |
Nu behövs vår magiska kontroll för att hämta rätt språk för rätt inställning. Vi börjar med funktionen som hämtar rätt språk.
1 2 3 4 5 6 | Shared Function GetLanguage() As String Select Case System.Web.HttpContext.Current.Session( "UserCurrentLanguage" ) Case Language.English : GetLanguage = "en" Case Else : GetLanguage = "sv" End Select End Function |
Rutinen returnerar helt enkelt en eller sv som vi sedan kommer använda oss av i vår språkfunktion när vi hämtar ut datat från vår xmlfil. Om nu anvädaren tittar på sidan och inte har påbörjat sin session än kan man också ta reda på vad webbläsaren har default för språk. Detta gör du exempelvis genom att kalla en rutin som nedan men endast en gång.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Shared Function GetLanguageId() As Integer GetLanguageId = CInt (System.Web.HttpContext.Current.Session( "UserCurrentLanguage" )) If GetLanguageId < 1 Then ' vilket språk är webbläsaren inställd på som default, visa då det först? Dim webbrowserlanguage As String = System.Web.HttpContext.Current.Request.ServerVariables( "HTTP_ACCEPT_LANGUAGE" ) Select Case Left(LCase(webbrowserlanguage), Language.English) Case "sv" System.Web.HttpContext.Current.Session( "UserCurrentLanguage" ) = Language.Swedish Case "en" System.Web.HttpContext.Current.Session( "UserCurrentLanguage" ) = Language.English Case Else System.Web.HttpContext.Current.Session( "UserCurrentLanguage" ) = Language.English End Select End If End Function |
Denna rutinen kan du exempelvis kalla på initiellt för att sedan användas vidare i dina rutiner. Den skall alltså kallas första gången, såvida du inte alltid vill påtvinga språket som webbläsaren initiellt använder. Därefter behöver vi också få reda på var filen finns placerat rent fysiskt på disk i vår applikation.
1 2 3 4 5 6 7 | Shared Function GetLanguagePath() As String If InStr(HttpContext.Current.Request.Url.ToString, "localhost" ) > 0 Then GetLanguagePath = ConfigurationSettings.AppSettings( "LanguageFileLocal" ) Else GetLanguagePath = ConfigurationSettings.AppSettings( "LanguageFile" ) End If End Function |
Denna hämtar helt enkelt ut sökvägen där vi placerat vår xmlfil. Du kan antingen använda dig av mappath eller så lägger du det helt enkelt i web.config. Jag förordar web.config eftersom mappath faktiskt gör en findfile som i sin tur tar cpu och disk IO i onödan. Det kommer bli ganska många anrop när folk surfar på sidan så allt sådant som är tidsbesparande skall användas.
1 2 3 4 5 6 7 | <? xml version = "1.0" encoding = "utf-8" ?> < configuration > < appSettings > < add key = "LanguageFile" value = "c:\server\wwwroot\include\extended.xml" /> < add key = "LanguageFileLocal" value = "c:\inetpub\wwwroot\include\extended.xml" /> </ appSettings > < system.web > |
Sådär, nu har vi även våra sökvägar i web.config. Nästa moment är nu aktiskt att återanvända till vår linkbutton ovan, för den skall ju byta språk på texten varje gång man klickar på denna. Vi tar helt enkelt och på vår huvudsida, exempelvis default.aspx att lägga till denna funktion.
1 | lnkLanguage.Text = Namespace .Klassnamn.GetLangMessage( "/page/language" , True ) |
Vi är nu bara en liten bit kvar från vår färdiga språkfil och den sista funktionen är alltså GetLangMessage som vi använder för att få tag på vår xml-fils specifika post beroende på vilket språk vi valt. Den här rutinen är lite mer avancerad än dom tidigare och en kort förklaring krävs innan vi summerar allting.
XML-filen läses in i cacheobjektet rakt upp och ner. Så länge den finns kommer vi hämta den och plocka ut vår översättning. Men om den inte finns där, läses den upp igen och läggs in. Jag har valt att med 15 minuters intervall döda denna cache och ladda om den automatiskt. Anledningen till det är att om jag skickar in en ny språkfil exempelvis via FTP så vet jag att inom 15 minuter kommer den nya användas. Hur du gör får du bestämma själv förstås. men jag tycker det är ett smidigt sätt.
En sak till, jag har något som kallas fallback. Det innebär att om jag står på sidan på Engelska och skall visa namnet där och det inte finns, då går jag vidare till Svenska och visar det. Finns inte det heller så skriver jag helt enkelt [nyckeln saknas]. På detta sätt vet du att du kanske missat ett stavat fe l på något tagg i din xml-fil, eller helt enkelt glömt den när du hoppar mellan dom olika språken. Nu tar vi en titt på vår funktion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | Shared Function GetLangMessage( ByVal KeyName As String , Optional ByVal FixBlankingSpace As Boolean = False ) As String ' // Hämtar ur translate.xml ett namn eller en text beroende ' på hur användaren ställt in sin webbläsare, eller via ' hemsidan. Dokumentet läggs även i en application-cache ' så den endast efterfrågas då vid av applikation/iis. ' ------------------------------------------------------ ' KeyName är case-sensitive ' rooten anges ej, är <languages> ' ------------------------------------------------------ Dim XMLdoc As XmlDocument = New XmlDocument Try ' // Om hela xmldokumentet är cachat så använder vi det, annars öppnar vi ' det bara igen och läser igenom våra värden If Not HttpContext.Current.Cache( "languagefile" ) Is Nothing Then ' läs ut xmlfilen från cache XMLdoc = HttpContext.Current.Cache( "languagefile" ) Else ' skriv ner xmlfilen i cache, cacha i 15 minuter XMLdoc.Load(GetLanguagePath) HttpContext.Current.Cache.Insert( "languagefile" , XMLdoc, Nothing , DateTime.Now.AddMinutes(15), TimeSpan.Zero) End If ' // Finns något data? If XMLdoc.DocumentElement Is Nothing Then Exit Function End If Catch ex As Exception ' vi fick något fel System.Web.HttpContext.Current.Response.Write( "Fel vid hämtning av xml-fil till siten. " & Err.Source & "<br>" & Err.Description) HttpContext.Current.Response. End () End Try ' // Söker ut rätt data baserat på frågan. En fallback finns vilket innebär ' att om inte något annat språks värde finns, söker den efter det svenska ' och finns inte det - returneras texten [nyckel saknas] Dim returnvalue As String Try returnvalue = XMLdoc.SelectSingleNode( "/languages/language[@id='" & UCase(pellesoft.Site.GetLanguage,) & "']/" & KeyName).InnerText Catch ex As Exception ' fallback på språk If pellesoft.Site.GetLanguageId >= 1 Then Try returnvalue = XMLdoc.SelectSingleNode( "/languages/language[@id='SV']/" & KeyName).InnerText Catch ex returnvalue = "[nyckel saknas]" End Try Else returnvalue = "[nyckel saknas]" End If End Try ' // rensar objektet XMLdoc = Nothing ' // returnerar resultatet If FixBlankingSpace = True Then returnvalue = Replace(returnvalue, "-" , "-" ) Return Replace(returnvalue, " " , " " ) Else Return returnvalue End If End Function |
Det som kan vara lite främmande här är ett scriptspråk som heter XPath och kan användas för att arbeta med just xml-filer. Funktionen SelectSingleNode söker den först matchande noden som kan finnas och vi skickar in data som säger att language skall vara SV/ därefter skall noden vara något som vi skickar med från vår funktion när vi skall skriva vår label. Det bör även tilläggas att Cache.Insert motsvarar gamla asp's Application("namn") men är nu mycket mer avancerad och effektivare.
Lycka till!
/Pelle Johansson
Svara
Sv:Språkhantering
Suveränt, tack för ditt svar (din artikel) :)
En fråga bara, när det gäller språkhantering och mest effektisvaste o prestanda snålaste metod. Hur står sig xml vs databas?
Svara
Sv: Språkhantering
Varsågod,
Idag går anropen så snabbt i en databas att du oftast inte behöver tänka på det. Dock, om det nu är så att du skall läsa en mängd labels i en databas för varje sidvisning så kommer det nog bli några anrop. dock kan du säkert som jag gjort, läsa ut information från databasen till ett dataset som du sedan cachar på sajten och läser från. Det blir i princip samma effekt som att ha xml-dokumentet cachat.
Det är inte så svårt att skriva om samma sak från XML till ett select-anrop från en tabell och därefter ändra själva "uttaget" av den/de labels som du behöver komma åt.
Svara