How can I transform a website to be able to handle multi language (example : english, french, spanish)?
I do not like the resource file because I feel limited and it's pretty long to build the list. Do you have any suggestion?
Update
For the moment the best way we found is to use an XML file and with some Xpath et get values.
Implicit localization (on the Visual Studio - Tools menu - Generate Local Resources) is about as easy as it can be. Write your pages in your default language, pick the menu option, and your resource files are created and can be sent to someone to translate.
The resx file is just xml, so if the translation company wants you can transform it into (and out of) spreadsheets easily.
Using a databases instead of resx as your backing store is not difficult. Rick Strahl has a good explanation and example code for a database-driven localization provider here - there's a nice built in localization editor too with interface to Google translations and Babelfish.
We store resources for multilingual sites in a database. We've created a couple of tools to make it easy to create and access these. There's a custom ExpressionBuilder that allows us to use this syntax:
<asp:linkbutton runat='server' text='<%$ LanguageStrings:ClickMe%>' />
And a custom label that contains the default text, and adds a row to the database if there's not already one.
<r:languagelabel runat="server" name="AboutUs">About Us</r:languagelabel>
The table containing the strings has one column per language. This makes it very easy to create the site in English (or whatever the default language is), then hand off the table (which populates itself) to a translator. It's also very easy to see what languages you need to have stuff translated for. With resources, every time you need to add a new string, you have to stop what you're doing, and then go to the resource file for each language and add the resource.
Here's the code for the language label:
''' <summary>
''' Retrieves a language-specific string.
''' </summary>
Public Class LanguageLabel
Inherits Label
Private _Name As String
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
Private Sub Populate()
If Len(Me.Name) > 0 Then
Dim LanguageString As String = GetLanguageString(Me.Name, Me.Text)
If Len(LanguageString) > 0 Then Me.Text = LanguageString
End If
End Sub
Private Sub LanguageLabel_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
Populate()
End Sub
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
' By default a label wraps the text in a <span>, which we don't want in some situations
writer.Write(Me.Text)
End Sub
End Class
and the utility function:
Public Function GetLanguageString(ByVal Name As String, Optional ByVal DefaultText As String = "") As String
Dim DefaultLanguage As Language = Languages.GetById(1)
Name = StripPunctuation(Name).Trim.Replace(" ", "") ' Remove punctuation, spaces from name
Dim SelectSql As String = String.Format("Select {0},{1} from LanguageStrings where Name=#Name", Languages.CurrentLanguage.Code, DefaultLanguage.Code)
Dim LanguageStringTable As DataTable = ExecuteDataset(cs, CommandType.Text, SelectSql, New SqlParameter("#Name", Name)).Tables(0)
If LanguageStringTable IsNot Nothing AndAlso LanguageStringTable.Rows.Count > 0 Then
Dim LanguageText As String = LanguageStringTable.Rows(0)(Languages.CurrentLanguage.Code).ToString
Dim DefaultLanguageText As String = LanguageStringTable.Rows(0)(DefaultLanguage.Code).ToString
If Len(LanguageText) > 0 Then
' We have a string in this language
Return LanguageText
Else
' Nothing in this language - return default language value
Return DefaultLanguageText
End If
Else
' No record with this name - create a dummy one
If DefaultText = "" Then DefaultText = Name
Dim InsertSql As String = String.Format("Insert into LanguageStrings (Name, {0}) values (#Name, #Text)", DefaultLanguage.Code)
ExecuteNonQuery(cs, CommandType.Text, InsertSql, New SqlParameter("#Name", Name), New SqlParameter("#Text", DefaultText))
Return Name
End If
End Function
Resource files are the way to go. We ship our product in 12 languages. We pull all strings out into resource files and ship them to a translation company. It's a pain at times, but that is the defacto way to do it.
It also gets fun when 4-letter English words get translated into 17-letter phrases and you have to tweak your UI.
How late in the design process are you? If not too late, and if the budget allows, consider porting to a multi-lingual CMS like Ektron CMS300.net (which has built-in translation tools). If not, then you've got a huge task ahead of you.
Another solution I am using is to create the language folders which contain the aspx pages containing all the required text in that particular language.
The only problem here is how can you inject as little code as possible into those replicating pages. I am using a controller pattern here to do this, and then a object data source to get the data and bind it to the controls in all pages.
In this way I have achieved the goal of getting rid of the resource files and I can keep the code behind in one place without replicating it (unless necessary).
Edit: I would recommend a good CMS framework as well.
One of the web apps I develop has this NLS requirement too.
I found that there are at least 3 locations where you have localized texts:
user interface
database tables ("catalogs" or whatever you want to call them)
backend code (services etc)
My solution has one table for the pages, tables, etc ("Container"), one table for each item in that container (e.g. labels, buttons by ID, record identifiers), and one table for the translated items (plus language identifier).
A translation application helps me keep the translations up-to-date, and exports all translations in XML.
The product ships with translations, but customers can adjust the translations, changes taking effect immediately.
Sample code i have done using resource file
add global.asax
void Application_BeginRequest(Object sender, EventArgs e)
{
// Code that runs on application startup
HttpCookie cookie = HttpContext.Current.Request.Cookies["CultureInfo"];
if (cookie != null && cookie.Value != null)
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cookie.Value);
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cookie.Value);
}
else
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en");
}
}
http://satindersinght.blogspot.in/2012/06/create-website-for-multilanguage.html
http://satindersinght.wordpress.com/2012/06/14/create-website-for-multilanguage-support/
Related
I am attempting to write a method using VB.NET that will allow me to read in a URL and compare it to a list. If it is one of the URLs on the list then Bing Tracking conversion will be applied.
At the moment I can only think do write it as a comaparative method, comapring the current URL with the ones that require tracking (a list). This, however, sems a little long winded.
Each page may have a different querystring value/page id, there for its fundamental to get exactly the right page for the tracking to be applied to.
Any Ideas?
Sorry I really am a novice when developing functions in VB.Net
If I were to use th Contains() function then I would imagine that it would look a little something like this:
Private sub URL_filter (ByVal thisPage As ContentPage, brandMessage? As Boolean) As String
Dim url_1 As String = "/future-contact thanks.aspx"
Dim url_2 As String = "/find-enquiry thanks.aspx?did=38"
Dim url_3 As String = "/find-enquiry-thanks.aspx?did=90"
Dim url_4 As String = "/find-enquiry-thanks.aspx?did=62"
Dim result as String
result = CStr (url_1.Contains(current_URL))
txtResult.Text = result
End Sub
If I were to use this then what type of loop would I have to run to check all the URLs that are in my list against the current_URL? Also where would I define the current_URL?
You can use the Contains() function to check if the list contains the given value. You could also implement a binary search, but it is probably overkill for your purposes. Here is an example:
Dim UrlList As New List(Of String)
UrlList.Add("www.example2.net") 'Just showing adding urls to the list
UrlList.Add("www.example3.co.uk")
UrlList.Add("www.exampletest.com")
Dim UrlToCheck As String = "www.exampletest.com" 'This is just an example url to check
Dim result As Boolean = UrlList.Contains(UrlToCheck) 'The result of whether it was found
Make sure to add these imports Imports System and Imports System.Collections.Generic
Disclaimer: I have no experience with VB.NET
Our client is asking to encrypt the URL because it is passing values in the query string. We have used encryption and are able to encrypt the URL; however, existing code uses querystring["var"] in so many places and fails because of the encrypted URL. Hence, on page load, we will have to decrypt the URL. If I decrypt and alter the query string using response.redirect, then again query string will be visible in the URL and can be misused.
Please help.
EDIT
I was reading about RESTfull web service. I have not yet understood entire concept. I wonder if I can use this with my application to hide query string. Please let me know if so.
Thanks.
One way to achieve this with little headache is to decrypt the query string as you currently do, then set its values to some object which can be stored in the session. Storing it in a session variable would be useful if you wanted to exclude this information (hide) from the query string - you'd essentially be passing the data around behind the scenes.
Once stored in session, you would then change your code, such that wherever you use querystring["var"], you will instead refer to the object that has been stored in the session.
Edit
Note, though, that this doesn't have to be relegated to a single value. This object can have multiple properties each representing a query string value:
MyQueryStringObject myQueryStringObject = new MyQueryStringObject(SomeUrl);
//MyQueryStringObject decrypts the query string and assigns the values to properties in its constructor
string abc = myQueryStringObject.abc;
string xyz = myQueryStringObject.xyz;
Now, that uses properties to represent each query string value. You may have tons of them. In that case, you can store the values into some sort of Dictionary or a NameValueCollection perhaps.
There are various ways to achieve this which I think is beyond topic, but, note that the key to all of this, the very essence is to simply decrypt the url on the server (during postback) and save the unencrypted data into a session variable should you want to hide it from the URL.
There is a much better way of going about this. I deal with a client with that has the same requirement. This class has soared through security scans as well.
Public Class QueryStringManager
Public Shared Function BuildQueryString(ByVal url As String, ByVal queryStringValues As NameValueCollection) As String
Dim builder As New StringBuilder()
builder.Append(url & "?")
Dim count = queryStringValues.Count
If count > 0 Then
For Each key In queryStringValues.AllKeys
Dim value As String = queryStringValues(key)
Dim param As String = BuildParameter(key, value)
builder.Append(param)
Next
End If
Return builder.ToString()
End Function
Public Shared Function DeconstructQueryString(ByVal Request As HttpRequest) As NameValueCollection
Dim queryStringValues As New NameValueCollection
For Each key In Request.QueryString.AllKeys
Dim value As String = Request.QueryString(key)
value = DeconstructParameter(value)
queryStringValues.Add(key, value)
Next
Return queryStringValues
End Function
Private Shared Function BuildParameter(ByVal key As String, ByVal value As String) As String
Dim builder As New StringBuilder()
builder.Append(key.ToString() & "=")
value = GetSafeHtmlFragment(value)
Dim encrypt As Security = New Security()
value = encrypt.Encrypt(value)
builder.Append(value)
builder.Append("&")
Return builder.ToString()
End Function
Public Shared Function DeconstructParameter(ByVal value As Object) As String
Dim decrypt As New Security()
value = decrypt.Decrypt(value)
value = GetSafeHtmlFragment(value)
End Function
End Class
Use
Dim nvc As NameValueCollection = New NameValueCollection()
nvc.Add("value", 1)
Dim builtUrl As String = QueryStringManager.BuildQueryString(url, nvc)
Response.Redirect(builtUrl, false);
Then when you get to the page you simply write:
Dim decryptedValues As NameValueCollection = QueryStringManager.DeconstructQueryString(Request)
The reason why I use NameValueCollection is because that's the same type as QueryString. You can build on to the class to add an object into the QueryString based on it's properties and their values as well. This keeps all of the complex and tedious logic encapsulated away.
Sorry, I'm sure this is something that has been covered many times, but I can't find quite what I am after.
I have a single row data table which contains various settings which are used within my web system. I have been toying with turning this into an XML document instead of the single row datatable, would that make more sense?
Anyway, so, given that this is one record, there is a field called "locations," this field contains data as follows:
locationName1,IpAddress|locationName2,IpAddress|etc
The ipAddress is just the first 5 digits of the IP and allows me to ensure that logins to certain elements (admin section managed by staff) can only be accepted when connected from a company computer (ie using our ISP) - this is a largely unnecessary feature, but stops kids I employ logging in at home and stuff!
So, as the user logs in, I can check if the IP is valid by a simple SQL query.
SELECT ips FROM settings WHERE loginLocations LIKE '%" & Left(ipAddress, 5) & " %'
What I need to be able to do now, is get the name of the users location from the dataField array.
I've come up with a few long winded looping procedures, but is there a simple way to analyse
locationName1,IpAddress1|locationName2,IpAddress2|etc
as a string and simply get the locationName where LoginIp = IpAddressX
... or am I going about this in a totally ridiculous way and should turn it into an xml file? (which will then create a whole load of other questions for you about parsing XML!!)
u can split the string in Vb.net and the send it to a query or anything
'Split the string on the "," character
Dim parts As String() = s.Split(New Char() {","c})
In SQL Server, these are the functions of interest in extracting a sub-string:
CHARINDEX
SUBSTRING
LENGTH
You can google the details about them (e.g. CHARINDEX at http://msdn.microsoft.com/en-us/library/ms186323.aspx). With these, you'll need to use computed columns in your query to extract that location name.
If it's not too late, you may want to devise a new way to layout these elements. For instance, instead of:
locationName1,IpAddress1|locationName2,IpAddress2|etc
How about
{IpAddress1,locationName1}{IpAddress2,locationName2}etc
That way you can use CHARINDEX to locate "{" & Left(ipAddress, 5); from that position, locate ","; then from that position locate the closing "}". From there it should be straightforward to use SUBSTRING to get at the locationName. Be forewarned that this will likely be a messy query (probably built from a few sub-queries, one for each position).
In the end, SpectralGhost's idea to just read in the column and do the extraction in VB is probably the way with the least hassle.
I've decided to do it this way, but would appreciate comments on efficiency.
I've split ipAddress and locationName into two database columns, within the one row, and getting the required data by comparing strings as arrays, as per the code below.
For this particular application I'm only dealing with one record, so it's pretty simple. However, further down the line, I need to product a system for monitoring product sales. from an invoices table.
Each invoice record is a row in the database with items in the invoice held similarly, [item1],[item2] etc. There is another column for quantities [qty1],[qty2] etc, prices [price1],[price2], etc. I'll need to be able to search the database for invoices in which an item number occurs (easy SQL WHERE itemList LIKE %[invNO]%) and then compare arrays to get the quantities and individual prices of each item on that invoice. Extracting these rows as arrays and locating the relevant position in these as per the code below, will work fine, but when the whole operation is looping through several hundred or thousand rows, will this become really slow?
ipList, locationList = list as comma separated string from database record field
Dim ipArray As Array = Split(ipList, ",")
Dim locationArray As Array = Split(locationList, ",")
For i = 0 To UBound(ipArray)
If Left(ipArray(i), 5) = Left(ipAddress, 5) Then
arrayPosition = i
itemFound = "True"
Exit For
End If
Next
location = locationArray(arrayPosition)
'loop through ips then get the position and make the location equal to that
If itemFound <> "True" Then
inValidIP = "True"
End If
I strongly advise that you break those our into database columns and each location is a separate row; had it been done that way in the first place, you wouldn't have the problem you have now.
But since you have it in a single row/column, why not bring that back and simply get the value from .NET?
Dim Source As String = "TestLocationA,127.0|TestLocationB,128.0|TestLocationC,129.0"
Dim Test As String = Source
Dim ToFind As String = "127.0"
Test = Test.Substring(0, Test.IndexOf(ToFind & "|") - 1)
Test = Test.Substring(Test.LastIndexOf("|") + 1)
MsgBox(Test)
OR
Public Class Form1
Private IpLocationList As New List(Of IpLocation)
Private IpToFind As String = ""
Private Class IpLocation
Public Name As String
Public IP As String
Public Sub New(ByVal FullLocation As String)
Me.Name = FullLocation.Substring(0, FullLocation.IndexOf(","))
Me.IP = FullLocation.Substring(FullLocation.IndexOf(",") + 1)
End Sub
End Class
Private Function FindIP(ByVal IpLocationItem As IpLocation) As Boolean
If IpLocationItem.IP = IpToFind Then
Return True
Else
Return False
End If
End Function
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim Source As String = "TestLocationA,127.0|TestLocationB,128.0|TestLocationC,129.0"
Dim LocationList As New List(Of String)
LocationList.AddRange(Split(Source, "|"))
For Each LocationItem As String In LocationList
IpLocationList.Add(New IpLocation(LocationItem))
Next
IpToFind = "127.0"
Dim result As IpLocation = IpLocationList.Find(AddressOf FindIP)
If result IsNot Nothing Then
MsgBox(result.Name)
End If
End Sub
End Class
enter code here
This has the benefit of loading the array one time and not needing to manually loop through arrays.
This is strange for me. I was able to set up the environment so that I can call .NET method (via COM) from a classic ASP page.
Everything actually works as intended until when I have to call a .NET method that requires a .NET type.
So I have a method named
I have a function like this in .Net
Public Sub SetTable(ByVal _City As City, ByVal _Country As Country)
'doing some thing
End Sub
and i have asp code like this:
dim CountryUtil, City, Country
set CountryUtil= Server.CreateObject("mydll.CountryUtil")
set city = Server.CreateObject("mydll.City")
set Country = Server.CreateObject("mydll.Country")
city.id= 123
city.property = "so and so"
Country.id= 123
Country.property = "so and so"
categoryUtil.SetTable(City, Country)
' I get this error here:
'Microsoft VBScript runtime error '800a0005'
'Invalid procedure call or argument: 'SetTable'
Thanks in advance.
if countryUtil is a class, you might have to initiate a new instance of it first.
Also, instead of Setting, you can just create new variables. Don't forget case sensitivity. If you try to pass the City instead of the city, it will give you problems.
''# Create a new instance of the categoryUtil Class
Dim countryUtil As New mydll.CountryUtil
Dim city As New mydll.City
Dim country As New mydll.Country
city.id= 123
city.property = "so and so"
country.id= 123
country.property = "so and so"
''# Instead of using the Class directly, you use the new instance of the Class
''# and pass the lowercase variables instead of the UpperCase Classes.
countryUtil.SetTable(city, country)
EDIT
If you were using a later version of the .NET framework, you could shorten it like this
''# Create a new instance of the categoryUtil Class
Dim countryUtil As New mydll.CountryUtil
Dim city As New mydll.City With {.id = 123, .property="so and so"}
Dim country As New mydll.Country With {.id=123, .property="so and so"}
''# Instead of using the Class directly, you use the new instance of the Class
''# and pass the lowercase variables instead of the UpperCase Classes.
countryUtil.SetTable(city, country)
EDIT
Check out this link to see how to mix asp and asp.net
http://knol.google.com/k/from-classic-asp-to-asp-net-and-back#
ASP values you set as values for the params are VARIANT.
But you defined different variable types in your function.
Example:
.NET Code:
Public Sub Test(param as String)
Classic ASP:
Dim yourParam : yourParam = "Testvalue"
YourClass.Test(yourParam)
This will fail.
Classic ASP:
Dim yourParam : yourParam = "Testvalue"
YourClass.Test(CStr(yourParam))
This will work.
So as resolution you need to take care to set the correct variable types while you call your function! In classic ASP everything is VARIANT.
Sidenote: Dictionary Objects, Array's are tricky to handle, I managed to define in .NET my variables as object[] and convert them inside the class.
I know my ASP.NET but i have to admit, i am dumb with classes and not sure how they work exactly. Also have not worked with them yet but i want to. But what I do know is that it's a place where i can keep code for re-use correct? How will my class look with my code?
So this is my code i use on about 3 forms - but i want to save it in 1 spot and just call it from like when i click on btnSubmit.
Dim strConnection As String = ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString
Dim con As SqlConnection = New SqlConnection(strConnection)
Dim cmd As SqlCommand = New SqlCommand
Dim objDs As DataSet = New DataSet
Dim dAdapter As SqlDataAdapter = New SqlDataAdapter
cmd.Connection = con
cmd.CommandType = CommandType.Text
cmd.CommandText = "SELECT distinct FIELD FROM TABLE order by FIELD"
dAdapter.SelectCommand = cmd
con.Open()
dAdapter.Fill(objDs)
con.Close()
If (objDs.Tables(0).Rows.Count > 0) Then
lstDropdown.DataSource = objDs.Tables(0)
lstDropdown.DataTextField = "FIELD"
lstDropdown.DataValueField = "FIELD"
lstDropdown.DataBind()
lstDropdown.Items.Insert(0, "Please Select")
lstDropdown2.Items.Insert(0, "Please Select")
Else
lblMessage.Text = "* Our Database seem to be down!"
End If
What must i put here to execute my code above?
Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
?????????????????????????????????
End Try
End Sub
Etienne
A class is (in VB.Net) is defined as so
Public Class Person
private _firstName as string
private _lastName as string
'''Constructor with no params
public Sub New()
_firstName = ""
_lastName = ""
End Sub
'Contructor with params
Public Sub New(FirstName as String, LastName as String)
_firstName = FirstName
_lastName = LastName
End Sub
Public Property FirstName As String
Get
return _firstName
End Get
Set(value as String)
_firstName = value
End Set
End Property
Public Property LastName As String
Get
return _lastName
End Get
Set(value as String)
_lastName = value
End Set
End Property
Public Function HitHomeRun() As Boolean
....'Do some stuff here
End Function
End Class
You can then instantiate the class and call its members.
Dim p as New Person()
p.FirstName = "Mike"
p.LastName = "Schmidt"
dim IsHomeRunHit As Boolean = p.HitHomeRun()
Learn more about creating and consuming classes in VB.Net.
This is a very big topic and can be defined in many different ways. But typically what you are venturing into is an N-Tier architecture.
Data Access Layer
Business Logic
UI Logic
Now the way a class can be built in your question can be done, but in the long run is prone to maintenance horror and modifiiability is cut short. Not to mention very much prone to bugs. Putting any type of data access code in your UI layer is bad practice.
This is where the power of having separate layers of classes (separation of concerns) in each layer gives you the ability to reuse code and ability to easily modify for future expansions/features etc. This is getting into Software Architecture is a very broad topic to put into one post.
But if you are really interested here are some links to point you into the right directions.
N-Tier Architecture from Wikipedia
Data Access Layer
Business Logic Layer
Martin Fowler is an expert in Architecture
There is software that eases the pain of the DAL.
1. Linq-To-SQL ability to query your data via .Net Objects (compiled queries)
2. Entity Framework Version 2 of Linq-To-SQL
And this effectively could replace all of your SQL code.
If you want to reuse the code, you should put it in a separate project. That way you can add that project to different solutions (or just reference the compiled dll).
In your web project you add a reference to the project (or to the dll if you have compiled it before and don't want to add the project to the solution).
In your new project you add a class file, for example named UIHelper. In the class skeleton that is created for you, you add a method. As the class is in a separate project, it doesn't know about the controls in the page, so you have to send references to those in the method call:
Public Shared Sub PopulateDropdowns(lstDropdown As DropDownList, lstDropdown2 As DropDownList)
... here goes your code
End Sub
In your page you call it with references to the dropdown lists that you have in the page:
UIHelper.PopulateDropdowns(lstDropdown, lstDropdown2)
This will get you started. There is a lot more to learn about using classes...
I sometimes create a "Common" class and put public Shared methods in it that I want to call from different places.
Something along these lines:
Public Class Common
Public Shared Sub MyMethod
'Do things.
End Sub
End Class
I'd then call it using:
Common.MyMethod
Obviously, you can a sub/function definition that takes the parameters you require.
Sorry if my VB.NET code is a bit off. I usually use C#.
I think you should look into using visual studio designer tools to do your data access and data binding. Search for typed datasets