Scrape webpage that requires md5 hash as a parameter - javascript

I am trying to scrape the data from the below link, in a c# console app:
https://www.eex-transparency.com/homepage/power/germany/production/availability/non-usability
Using the developer tools in chrome I can see that its possible to get a json response, the url to get this is:
https://www.eex-transparency.com/dsp/tem-12?country=de&expires=1454345128&md5=TRhtJei_go4ueLeekBc8yw
the website uses this js file (https://www.eex-transparency.com/assets/js/tpe-website.js) to generate the expires and md5 hash key. I think I've figured out that the expires value is a unix datetime. I have never used javascript before so finding it hard to figure out how they construct the md5.
The Javascript that generates these code is:
generateCryptedParams=function(url,clientIP)
{
var cryptedParams,md5,md5Encoded,md5WithoutSpeciaChars,parser,timePoint,urlPath;
return timePoint=moment().tz("Europe/Berlin").add(1,"minute").unix(),
parser=document.createElement("a"),
parser.href=url,
urlPath=parser.pathname,
"/"!==urlPath[0]&&(urlPath="/"+urlPath),
md5=CryptoJS.MD5(urlPath+timePoint+clientIP+" zYeHzBomGdgV"),
md5Encoded=md5.toString(CryptoJS.enc.Base64),
md5WithoutSpeciaChars=replaceSpecialChars(md5Encoded),
cryptedParams={"expires":timePoint,"md5":md5WithoutSpeciaChars}
}
replaceSpecialChars=function(str)
{
var key,specialChars,value;
specialChars={"=":"","\\+":"-","/":"_","%":"_"};
for(key in specialChars)
value=specialChars[key],
str=str.replace(new RegExp(key,"g"),value);
return str
}
As i said I think I'm comfortable with the timepoint part but the md5 is confusing me. Below is my C# code to replicate their but when I pass the md5 hash their site returns a 403 Forbidden error.
public Tuple<string, Int32> GenerateCrypto(string url, string ipAddress)
{
string cetId = "Central European Standard Time";
TimeZoneInfo cetZone = TimeZoneInfo.FindSystemTimeZoneById(cetId);
var CETDateTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cetZone);
//Int32 unixTimestamp = (Int32)(CETDateTime.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
Int32 unixTimestamp = (Int32)(DateTime.UtcNow.AddMinutes(1).Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
url = url.Split('/')[3];
var md5 = CipherUtility.GenerateMd5(url + unixTimestamp + ipAddress + " zYeHzBomGdgV");
var md5Encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(md5));
var md5withoutSpecialCharts = replaceSpecialChars(md5Encoded);
md5withoutSpecialCharts = md5withoutSpecialCharts.Substring(0, 22);
return new Tuple<string, Int32>(md5withoutSpecialCharts, unixTimestamp);
}

The solution was that I needed to concatenate a const string to all the elements before hashing it.

Related

Generate encoded docket number from two integers and decode it

I am trying to generate encoded docket number from storeId and transactionId. Encoded docket number has to be unique, length should be <=9 and easy to read/copy for users as well.
The maximum length of storeId is 3 and maximum length of transactionId is 5.
How can I improve my code so that my docket number will be unbreakable?
Here is my code:
let myTransKey = 19651;
let myStoreKey = 186;
function generateShortCode(storeId, transactionId) {
//reverse the ids and then add the respective key
var SID = storeId.toString().split("").reverse().join("");
SID = parseInt(SID) + myStoreKey;
var TID = transactionId.toString().split("").reverse().join("");
TID = parseInt(TID) + myTransKey;
var docketNum = `${SID}-${TID}`;
return docketNum;
}
function decodeShortCode(shortCode) {
shortCode = shortCode.split("-");
var storeID = shortCode[0];
var transactionID = shortCode[1];
//subtract the same key and then reverse the ids again
storeID = parseInt(storeID.toString()) - myStoreKey;
storeID = storeID.toString().split("").reverse().join("");
transactionID = parseInt(transactionID.toString()) - myTransKey;
transactionID = transactionID.toString().split("").reverse().join("");
return {
storeId: parseInt(storeID), // store id goes here,
shopDate: new Date(), // the date the customer shopped,
transactionId: parseInt(transactionID) // transaction id goes here
};
}
Is there any better way to do this? I need to encode docket number in a way which will be really hard to decode by any third person.
Every encrypted message can be broken if an attacker tries every possible decryption key (this is called a brute-force attack). With modern computers, this is really easy to do. The way that you are encoding data is very easy to break (within seconds). However, there are encryption methods that take very long to break (like millions of years long).
One of the more popular encryption algorithms is AES. Because it is so popular, there are also many easy-to-use libraries for JavaScript. Here's an example with CryptoJS:
const KEY = "a super secret password";
let myTransKey = 19651;
let myStoreKey = 186;
function generateShortCode(storeId, transactionId) {
const docketNum = `${storeId}-${transactionId}`;
return CryptoJS.AES.encrypt(docketNum, KEY).toString().replace("=", "");
}
function decodeShortCode(shortCode) {
const docketNum = CryptoJS.AES.decrypt(shortCode, KEY).toString(CryptoJS.enc.Utf8);
const parts = docketNum.split("-");
return {
storeId: parseInt(parts[0]), // store id goes here,
shopDate: new Date(), // the date the customer shopped,
transactionId: parseInt(parts[1]) // transaction id goes here
};
}
const s1 = generateShortCode(myStoreKey, myTransKey);
console.log("Short Code: " + s1);
console.log("Decrypted Short Code:", decodeShortCode(s1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js" integrity="sha256-/H4YS+7aYb9kJ5OKhFYPUjSJdrtV6AeyJOtTkw6X72o=" crossorigin="anonymous"></script>
This shortcode is longer than 9 characters, but it very secure and nearly unbreakable. This is really just the tradeoff. If you reduce the length of the shortcode, then you won't be able to have a secure shortcode. Users can still easily copy and paste the code though. If you absolutely need a shorter cipher, then try looking at Skip32.
Be sure to change KEY to a secret key that isn't shared with anyone. Also, be sure not to run this code client-side. If the encryption key is sent to the client, then they could look at the JavaScript code and then be able to decrypt any message.
well this work for me with visual compser in Wordpress
/[[^[]vc[^]]]/ig

MWS Post Request with Google Scripts

I am trying to make a post request through google scripts to amazon to collect information.
We are trying to get our orders to MWS and transfer them to sheets automatically.
I got to the last step which is signing the request.
A few things I wasnt sure about:
They say we use the secret key for hashing,I only see a client secret
and an access key id, which do I use?
Do I add the URL as part of whats getting signed? On the MWS Scratch pad they add this as shown:
POST
mws.amazonservices.com
/Orders/2013-09-01
Does it have to be on separate lines does it need post and the rest of the stuff. Its a little unclear.?
I read online that the sha256 byte code gets base64 encoded, not the string literal, is that true?
I tried to hash the string that amazon gave to me with an online tool and compare it to the hash they provided and the base64 encode, thing matched. I tried decoding as well, nothing matched
Can someone please send me an example that works, so I can understand what happens and how it works?
Thank you!
Below is what I have so far:
function POSTRequest() {
var url = 'https:mws.amazonservices.com/Orders/2013-09-01?';
var today = new Date();
var todayTime = ISODateString(today);
var yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
yesterday.setHours(0,0,0,0);
var yesterdayTime = ISODateString(yesterday);
var dayBeforeYesterday = new Date();
dayBeforeYesterday.setDate(today.getDate() - 2);
dayBeforeYesterday.setHours(0,0,0,0);
var dayBeforeYesterdayTime = ISODateString(dayBeforeYesterday);
var unsignedURL =
'POST\r\nhttps:mws.amazonservices.com\r\n/Orders/2013-09-01\r\n'+
'AWSAccessKeyId=xxxxxxxxxxx' +
'&Action=ListOrders'+
'&CreatedAfter=' + dayBeforeYesterdayTime +
'&CreatedBefore' + yesterdayTime +
'&FulfillmentChannel.Channel.1=AFN' +
'&MWSAuthToken=xxxxxxxxxxxx'+
'&MarketplaceId.Id.1=ATVPDKIKX0DER' +
'&SellerId=xxxxxxxxxxx'+
'&SignatureMethod=HmacSHA256'+
'&SignatureVersion=2'+
'&Timestamp='+ ISODateString(new Date) +
'&Version=2013-09-0';
var formData = {
'AWSAccessKeyId' : 'xxxxxxxxx',
'Action' : "ListOrders",
'CreatedAfter' : dayBeforeYesterdayTime,
'CreatedBefore' : yesterdayTime,
'FulfillmentChannel.Channel.1' : 'AFN',
'MWSAuthToken' : 'xxxxxxxxxxxx',
'MarketplaceId.Id.1' : 'ATVPDKIKX0DER',
'SellerId' : 'xxxxxxxxxx',
'SignatureMethod' : 'HmacSHA256',
'SignatureVersion' : '2',
'Timestamp' : ISODateString(new Date),
'Version' : '2013-09-01',
'Signature' : calculatedSignature(unsignedURL)
};
var options = {
"method" : "post",
"muteHttpExceptions" : true,
"payload" : formData
};
var result = UrlFetchApp.fetch(url, options);
writeDataToXML(result);
Logger.log(result);
if (result.getResponseCode() == 200) {
writeDataToXML(result);
}
}
function calculatedSignature(url) {
var urlToSign = url;
var secret = "xxxxxxxxxxxxxxxxxxx";
var accesskeyid = 'xxxxxxxxxxxxxxx';
var byteSignature = Utilities.computeHmacSha256Signature(urlToSign, secret);
// convert byte array to hex string
var signature = byteSignature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
Logger.log("URL to sign: " + urlToSign);
Logger.log("");
Logger.log("byte " + byteSignature);
Logger.log("");
Logger.log("reg " + signature);
var byte64 = Utilities.base64Encode(byteSignature)
Logger.log("base64 byte " + Utilities.base64Encode(byteSignature));
Logger.log("");
Logger.log("base64 reg " + Utilities.base64Encode(signature));
return byte64;
}
Step 1, creating the string to be signed
The string_to_sign is the combination of the following:
The string POST followed by a NEWLINE character
The name of the host, mws.amazonservices.com, followed by a NEWLINE
The API URL, often just /, or somthing like /Orders/2013-09-01, followed by a NEWLINE
An alphabetical list of all parameters except Signature in URL encoding, like a=1&b=2, not followed by anything
The minimum parameters seem to be the following:
AWSAccessKeyId is a 20-character code provided by Amazon
Action is the name of your API call, like GetReport
SellerId is your 14-character seller ID
SignatureMethod is HmacSHA256
SignatureVersion is 2
Timestamp is a date like 20181231T23:59:59Z for one second before new year UTC
Version is the API version like 2013-09-01
additional parameters may be needed for your call, depending on the value of Action
Please note:
The NEWLINE character is just "\n", not "\r\n"
The name of the host should not include "https://" or "http://"
The Signature parameter is necessary for the actual call later (see step 3), but is not part of the string_to_sign.
Your string_to_sign should now look somewhat like this:
POST
mws.amazonservices.com
/Orders/2013-09-01
AWSAccessKeyId=12345678901234567890&Action=ListOrders&CreatedAfter .... &Version=2013-09-01
Step 2, signing that string
Calculate a SHA256 hash of above string using the 40-character Secret Key
Encode this hash using Base64
In Pseudocode: signature = Base64encode( SHA256( string_to_sign, secret_key ))
Step 3, send the call
Send a HTTPS POST request, using the full alphabetical list of parameters, now including above signature as Signature somewhere in the middle, because you need to keep ascending alphabetical order.
https://mws.amazonservices.com/Orders/2013-09-01?AWSAccessKeyId....Version=2013-09-01
Step 4, processing the result
You should be getting two things back: a response header and a XML document. Be sure to evaluate the HTTP status in the header as well as all contents of the XML document. Some error messages are hidden deeply in XML while HTTP returns "200 OK".
Step 5, Advanced Stuff
If you use calls that require you to send a document, like SendFeed, you need to do the following additional steps:
Calculate the MD5 hash of your document
Encode this hash using Base64
In Pseudocode: contentmd5= Base64encode( MD5( document ))
Add Content-Type: text/xml (or whatever fits your document) as HTTP header
Add Content-MD5: plus the base64 encoded hash as HTTP header
This code was a great help for building a similar application. I corrected some small issues to bring it up to work:
before signing the ISODate need to be worked out to replace the ":" chars
var todayTime = ISODateString(today);
var todayISO = todayTime;
var todayTime_ = todayTime.replace(":", "%3A");
todayTime = todayTime_.replace(":","%3A");
The same for the calculated signature (quick & dirty solution, replace only 3 appearences and need to updated for more chars)
Logger.log(unsignedURL);
var tmpsignature = calculatedSignature(unsignedURL);
var orsignature = tmpsignature;
// encode special chars
tmpsignature = encodeURIComponent(orsignature);
}
I added a header and dropped the form, put all parameters in the url
var header = {
"x-amazon-user-agent": "GoogleSheets/1.0 (Language=Javascript)",
"Content-Type": "application/x-www-form-urlencoded"
// "Content-Type": "text/xml"
};
var options = {
"method" : "post",
"muteHttpExceptions" : true,
// "payload" : formData,
"header":header
};
And changed the call to url encoded (Form was not working, do not know why)
var url = 'https:mws-eu.amazonservices.com/Orders/2013-09-01?'+
'AWSAccessKeyId=<your stuff>'+
'&Action=GetOrder'+
'&SellerId=<your stuff>+
'&MWSAuthToken=<your token>'+
'&SignatureVersion=2'+
'&Timestamp='+todayTime'+ // remember to replace the ":" thru hex
'&Version=2013-09-01'+
'&Signature='+ tmpsignature+
'&SignatureMethod=HmacSHA256'+
'&AmazonOrderId.Id.1='+<your order;
parsed the response:
var result = UrlFetchApp.fetch(url, options);
//writeDataToXML(result);
Logger.log(result);
var xml = result.getContentText();
var document = XmlService.parse(xml);
This worked! :-)
I checked the signature also with
https://mws-eu.amazonservices.com/scratchpad/index.html

How to get X509Certificate thumbprint in Javascript?

I need to write a function in javascript (forge) that get a thumbnail of a pfx certificate. I created a test certificate(mypfx.pfx). By using c# X509Certificate2 library, I can see thumbprint of input certificate in X509Certificate2 object by passing file bytes array and password. Here is c# code snippet:
X509Certificate2 certificate = new X509Certificate2(byteArrayCertData, password);
var thumbprint = certificate.Thumbprint;
//thumbprint is a hex encoding SHA-1 hash
But when I am trying to do the same thing in javascript (using forge). I can't get a correct thumbprint. Here is my Javascript code:
var certi = fs.readFileSync('c:/mypfx.pfx');
let p12b64 = Buffer(certi).toString('base64');
let p12Der = forge.util.decode64(p12b64);
var outAsn1 = forge.asn1.fromDer(p12Der);
var pkcs12 = forge.pkcs12.pkcs12FromAsn1(outAsn1, false, "1234");
var fp = null;
for (var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
var safeContents = pkcs12.safeContents[sci];
for (var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
var safeBag = safeContents.safeBags[sbi];
if (safeBag.cert != undefined && safeBag.cert.publicKey != undefined) {
fp = forge.pki.getPublicKeyFingerprint(safeBag.cert.publicKey, {type: 'RSAPublicKey'});
//Is this fingerprint value I am looking for??
break;
}
}
}
The result is different value compare to c# thumbprint which seems to be wrong. I tried different functions in pkcs12.js file. None of them works. It's acceptable using other JS library as long as correct thumbprint result is produced. Please help and correct any mistakes I made. Thanks!
You are comparing different data. The certificate thumbprint is not the same that the public key fingerprint.
The certificate thumbprint is a hash calculated on the entire certificate. Seems forge does not have a method, but you can calculate yourself
//SHA-1 on certificate binary data
var md = forge.md.sha1.create();
md.start();
md.update(certDer);
var digest = md.digest();
//print as HEX
var hex = digest.toHex();
console.log(hex);
To convert the forge certificate to DER (binary) you can use this
var certAsn1 = forge.pki.certificateToAsn1(cert);
var certDer = forge.asn1.toDer(certAsn1).getBytes();

Twitter OAuth Request_Token with Javascript, possibly wrong time?

So I'm trying and failing to get a token from twitter.
I get the following error: "Failed to validate oauth signature and token".
I have read it can be due to your system clock being wrong.
In javascript I tested my date with the following code
var minutes=1000*60;
var hours=minutes*60;
var days=hours*24;
var years=days*365;
var d=new Date();
var t=d.getTime();
var y=t/years;
console.log((y+1970) + " year and " + (t%years)/days)
This gave me the year as 2012 and 17 days..
1972
1976
1980
1984
1988
1992
1996
2000
2004
2008
__=10 leap days. Today is the 8th, so taking away leap days it appears my system clock is on the 7th? Or have I made a mistake here? If this is the problem how do I fix correct my clock?
In cmd when I do the date cmd it gives me todays date, i.e the 8th.
Here is my post request and code in case the problem lies within the code and not the clock.
My Post request is:
POST http://api.twitter.com/oauth/request_token?oauth_callback=127.0.0.1&oauth_consumer_key=FFZJrBaPLsiwTDg5159tTQ&oauth_nonce=tWHEEIW8vLS6tMggo3IXe6e449qv1GpE8LunKRsbRF&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1326039495&oauth_version=1.0&oauth_signature=d%2BQqgTzJCjYIp9vKwm%2BCWzVLPvA
which gets 401 (Unauthorized)
Here is my javascript code.
var url = "http://api.twitter.com/oauth/request_token";
var params={
oauth_callback : "127.0.0.1"
,oauth_consumer_key : "FFZJrBaPLsiwTDg5159tTQ"
,oauth_nonce : OAuth.nonce(42)
,oauth_signature_method : "HMAC-SHA1"
,oauth_timestamp : OAuth.timestamp()
,oauth_version: "1.0"}
//temp is to be the signature base string
var temp = toSignParams("POST",url,params);
console.log(temp);
//This logs the signature base string as "POST&http%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3D127.0.0.1%26oauth_consumer_key%3DFFZJrBaPLsiwTDg5159tTQ%26oauth_nonce%3D5gQVIa3WmwD6ARGGQTITl1Ozgxe2t8em5HC7g8wvMi%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1326038871%26oauth_version%3D1.0"
//which is correct I think.
//When I use this with the base signature from twitters oauth example page I get the result they got.
//it hashes the twitter signing key with base signature.
params.oauth_signature = b64_hmac_sha1("MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98&",temp);
var req = new XMLHttpRequest();
req.open("POST",toURIParams(url,params),true);
req.send();
console.log(params)
req.onreadystatechange=function(){
if (req.readyState==4)
{
console.log(req.responseText); //this is saying "Failed to validate oauth signature and token"
}
}
//function to convert to Signature paramaters, as indicated on twitter page.
function toSignParams(method,base,params){
tail=[];
for (var p in params) {
if (params.hasOwnProperty(p)) {
tail.push(p + "%3D" + encodeURIComponent(params[p]));
}
}
return method + "&" + encodeURIComponent(base) + "&" + tail.join("%26")
}
//function to convert to uri encoded parameters.
function toURIParams(base, params) {
tail = [];
for (var p in params) {
if (params.hasOwnProperty(p)) {
tail.push(p + "=" + encodeURIComponent(params[p]));
}
}
return base + "?" + tail.join("&")
}
Any ideas?
I used a library called "jsOAuth-1.3.3" as was found on the twitter list of libraries compatible with JavaScript but is no longer there.
The 2 javascript options they list now are:
user-stream by #AivisSilins — a simple Node.js User streams client
and
codebird-js by #mynetx — a Twitter Library in JScript.
as found on https://dev.twitter.com/docs/twitter-libraries
If anybody has a problem where they can't get either of those solutions to work, I can try find online or upload the library I used somewhere.

Localize Strings in Javascript

I'm currently using .resx files to manage my server side resources for .NET.
the application that I am dealing with also allows developers to plugin JavaScript into various event handlers for client side validation, etc.. What is the best way for me to localize my JavaScript messages and strings?
Ideally, I would like to store the strings in the .resx files to keep them with the rest of the localized resources.
I'm open to suggestions.
A basic JavaScript object is an associative array, so it can easily be used to store key/value pairs. So using JSON, you could create an object for each string to be localized like this:
var localizedStrings={
confirmMessage:{
'en/US':'Are you sure?',
'fr/FR':'Est-ce que vous êtes certain?',
...
},
...
}
Then you could get the locale version of each string like this:
var locale='en/US';
var confirm=localizedStrings['confirmMessage'][locale];
Inspired by SproutCore You can set properties of
strings:
'Hello'.fr = 'Bonjour';
'Hello'.es = 'Hola';
and then simply spit out the proper localization based on your locale:
var locale = 'en';
alert( message[locale] );
After Googling a lot and not satisfied with the majority of solutions presented, I have just found an amazing/generic solution that uses T4 templates. The complete post by Jochen van Wylick you can read here:
Using T4 for localizing JavaScript resources based on .resx files
Main advantages are:
Having only 1 place where resources are managed ( namely the .resx
files )
Support for multiple cultures
Leverage IntelliSense - allow for code completion
Disadvantages:
The shortcomings of this solution are of course that the size of the
.js file might become quite large. However, since it's cached by the
browser, we don't consider this a problem for our application. However
- this caching can also result in the browser not finding the resource called from code.
How this works?
Basically he defined a T4 template that points to your .resx files. With some C# code he traverses each and every resource string and add it to JavaScript pure key value properties that then are output in a single JavaScript file called Resources.js (you can tweak the names if you wish).
T4 template [ change accordingly to point to your .resx files location ]
<## template language="C#" debug="false" hostspecific="true"#>
<## assembly name="System.Windows.Forms" #>
<## import namespace="System.Resources" #>
<## import namespace="System.Collections" #>
<## import namespace="System.IO" #>
<## output extension=".js"#>
<#
var path = Path.GetDirectoryName(Host.TemplateFile) + "/../App_GlobalResources/";
var resourceNames = new string[1]
{
"Common"
};
#>
/**
* Resources
* ---------
* This file is auto-generated by a tool
* 2012 Jochen van Wylick
**/
var Resources = {
<# foreach (var name in resourceNames) { #>
<#=name #>: {},
<# } #>
};
<# foreach (var name in resourceNames) {
var nlFile = Host.ResolvePath(path + name + ".nl.resx" );
var enFile = Host.ResolvePath(path + name + ".resx" );
ResXResourceSet nlResxSet = new ResXResourceSet(nlFile);
ResXResourceSet enResxSet = new ResXResourceSet(enFile);
#>
<# foreach (DictionaryEntry item in nlResxSet) { #>
Resources.<#=name#>.<#=item.Key.ToString()#> = {
'nl-NL': '<#= ("" + item.Value).Replace("\r\n", string.Empty).Replace("'","\\'")#>',
'en-GB': '<#= ("" + enResxSet.GetString(item.Key.ToString())).Replace("\r\n", string.Empty).Replace("'","\\'")#>'
};
<# } #>
<# } #>
In the Form/View side
To have the correct translation picked up, add this in your master if you're using WebForms:
<script type="text/javascript">
var locale = '<%= System.Threading.Thread.CurrentThread.CurrentCulture.Name %>';
</script>
<script type="text/javascript" src="/Scripts/Resources.js"></script>
If you're using ASP.NET MVC (like me), you can do this:
<script type="text/javascript">
// Setting Locale that will be used by JavaScript translations
var locale = $("meta[name='accept-language']").attr("content");
</script>
<script type="text/javascript" src="/Scripts/Resources.js"></script>
The MetaAcceptLanguage helper I got from this awesome post by Scott Hanselman:
Globalization, Internationalization and Localization in ASP.NET MVC 3, JavaScript and jQuery - Part 1
public static IHtmlString MetaAcceptLanguage<T>(this HtmlHelper<T> html)
{
var acceptLanguage =
HttpUtility.HtmlAttributeEncode(
Thread.CurrentThread.CurrentUICulture.ToString());
return new HtmlString(
String.Format("<meta name=\"{0}\" content=\"{1}\">", "accept-language",
acceptLanguage));
}
Use it
var msg = Resources.Common.Greeting[locale];
alert(msg);
With a satellite assembly (instead of a resx file) you can enumerate all strings on the server, where you know the language, thus generating a Javascript object with only the strings for the correct language.
Something like this works for us (VB.NET code):
Dim rm As New ResourceManager([resource name], [your assembly])
Dim rs As ResourceSet =
rm.GetResourceSet(Thread.CurrentThread.CurrentCulture, True, True)
For Each kvp As DictionaryEntry In rs
[Write out kvp.Key and kvp.Value]
Next
However, we haven't found a way to do this for .resx files yet, sadly.
JSGettext does an excellent job -- dynamic loading of GNU Gettext .po files using pretty much any language on the backend. Google for "Dynamic Javascript localization with Gettext and PHP" to find a walkthrough for JSGettext with PHP (I'd post the link, but this silly site won't let me, sigh...)
Edit: this should be the link
I would use an object/array notation:
var phrases={};
phrases['fatalError'] ='On no!';
Then you can just swap the JS file, or use an Ajax call to redefine your phrase list.
There's a library for localizing JavaScript applications:
https://github.com/wikimedia/jquery.i18n
It can do parameter replacement, supports gender (clever he/she handling), number (clever plural handling, including languages that have more than one plural form), and custom grammar rules that some languages need.
The strings are stored in JSON files.
The only requirement is jQuery.
I did the following to localize JavaScript for a mobile app running HTML5:
1.Created a set of resource files for each language calling them like "en.js" for English. Each contained the different strings the app as follows:
var localString = {
appName: "your app name",
message1: "blah blah"
};
2.Used Lazyload to load the proper resource file based on the locale language of the app: https://github.com/rgrove/lazyload
3.Pass the language code via a Query String (As I am launching the html file from Android using PhoneGap)
4.Then I wrote the following code to load dynamically the proper resource file:
var lang = getQueryString("language");
localization(lang);
function localization(languageCode) {
try {
var defaultLang = "en";
var resourcesFolder = "values/";
if(!languageCode || languageCode.length == 0)
languageCode = defaultLang;
// var LOCALIZATION = null;
LazyLoad.js(resourcesFolder + languageCode + ".js", function() {
if( typeof LOCALIZATION == 'undefined') {
LazyLoad.js(resourcesFolder + defaultLang + ".js", function() {
for(var propertyName in LOCALIZATION) {
$("#" + propertyName).html(LOCALIZATION[propertyName]);
}
});
} else {
for(var propertyName in LOCALIZATION) {
$("#" + propertyName).html(LOCALIZATION[propertyName]);
}
}
});
} catch (e) {
errorEvent(e);
}
}
function getQueryString(name)
{
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.href);
if(results == null)
return "";
else
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
5.From the html file I refer to the strings as follows:
span id="appName"
Well, I think that you can consider this. English-Spanish example:
Write 2 Js Scripts, like that:
en-GB.js
lang = {
date_message: 'The start date is incorrect',
...
};
es-ES.js
lang = {
date_message: 'Fecha de inicio incorrecta',
...
};
Server side - code behind:
Protected Overrides Sub InitializeCulture()
Dim sLang As String
sLang = "es-ES"
Me.Culture = sLang
Me.UICulture = sLang
Page.ClientScript.RegisterClientScriptInclude(sLang & ".js", "../Scripts/" & sLang & ".js")
MyBase.InitializeCulture()
End Sub
Where sLang could be "en-GB", you know, depending on current user's selection ...
Javascript calls:
alert (lang.date_message);
And it works, very easy, I think.
Expanding on diodeus.myopenid.com's answer: Have your code write out a file containing a JS array with all the required strings, then load the appropriate file/script before the other JS code.
The MSDN way of doing it, basically is:
You create a separate script file for each supported language and culture. In each script file, you include an object in JSON format that contains the localized resources values for that language and culture.
I can't tell you the best solution for your question, but IMHO this is the worst way of doing it. At least now you know how NOT to do it.
We use MVC and have simply created a controller action to return a localized string. We maintain the user's culture in session and set the thread culture before any call to retrieve a language string, AJAX or otherwise. This means we always return a localized string.
I'll admit, it isn't the most efficient method but getting a localised string in javascript is seldom required as most localization is done in our partial views.
Global.asax.cs
protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
{
// Set the current thread's culture
var culture = (CultureInfo)Session["CultureInfo"];
if (culture != null)
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
}
}
Controller Action
public string GetString(string key)
{
return Language.ResourceManager.GetString(key);
}
Javascript
/*
Retrieve a localized language string given a lookup key.
Example use:
var str = language.getString('MyString');
*/
var language = new function () {
this.getString = function (key) {
var retVal = '';
$.ajax({
url: rootUrl + 'Language/GetString?key=' + key,
async: false,
success: function (results) {
retVal = results;
}
});
return retVal;
}
};

Categories

Resources