所有我感兴趣的资料,webdesign,webdev,linux,etc.Enjoy。
With the release of the beta version of the Opera Widgets SDK, developers can get started on making widgets for mobile. So far UIQ 3.3 supports widgets, and support on more platforms are on the way. The SDK contains among other things:
Documentation We added 10 more articles and documentation about widgets, especially focusing on mobile and cross device development. See the SDK overview page for more information. Tools The new Widget emulator will emulate how your widget will work on different devices. And it's implemented as a widget! You can also use Opera Dragonfly to debug your widget, even when they are running in the emulator. Libraries You'll find several libraries on the Libraries section on dev.opera.com which helps speed up development. This list will expand as we go forward.And it's all on the web! Each article and tool is available from Opera's developer website dev.opera.com.
Widget emulator and DragonflyThe Widget emulator is a tool for seeing how your widgets will look like and behave on different devices. It emulates screen size, media query behavior, the docked widget mode as well as aspects such as network speed and the size of the preference store.
Development for mobile is made quicker and easier by not having to transfer the widget to test on the actual device. Instead, you can just reload your widget in the emulator and test on the phone when you're happy with the resuts.
By using Opera Dragonfly, you can also debug your widgets. They show up as runtimes just like the webpages in your tabs.
Widgets on mobile and cross device developmentOpera Mobile 9.5 is on it's way, and with it, developers will be able to make widgets on mobile phones. Mobile widgets offer interesting possibilities for people on the move, as well as challenges when it comes to usability and performance.
For now, we've put up an overview of the characteristics of widgets on mobile phones and some advice for doing cross device development. There's also some advice for a mobile development process. With these, and the emulator, you can get a head start in developing for mobile.
What the future holdsWe have more in the works for extending the SDK with more useful tools and documentation. Some examples:
More libraries More libraries are in the works. For example, we have a library for internationalization and localization coming up and there is also a framework for more easily making resizable and flexible widget in the works. More functionality in the emulator We're working on simpler ways of loading your widget into the emulator, as well as emulating more of the functionality of widgets. More widget examples We're going to give you a few more widgets to play with, in addition to those you can already find on widgets.opera.com. More documentation and articles We'll improve on the current documents, as well as adding more articles. Examples include converting widgets from other runtimes to Opera widgets, more guidelines for mobiles, using Dragonfly with widgets and more.We also want to hear what you have to say, so do visit the Opera Widgets forums and tells us what you need!
We have just released a new version of widgets.opera.com where we have reworked our multi-language solution to support search engines. Instead of storing the user selected language in a cookie, which hides all languages except the default from search engines and indexers, we now have unique URLs for every language which the site is translated into.
In this blog post we present both the important client-side effects, and our technical Python and Pylons-based solutions on the servers. So if you are a user of widgets.opera.com, a developer who wants a similar site structure, or just passing by - read on!
What has changed?Widgets.opera.com is currently available in two languages: English and Japanese. Before, the language you selected for the site was stored in a cookie. With this release, the cookie is removed and the URLs will instead reflect your choice of language. This means that all pages in Japanese have slightly different URLs than the corresponding pages in English. For example, the URL for the most popular widgets list is now:
As you can see, URLs without a language specifier (the same URLs as before) will give you the pages in English. The Japanese versions of the pages have the "ja" prefix just after the domain name. The same holds for all other pages on widgets.opera.com, and the links between them will adjust to your choice of language.
Why change?The problem with the cookie-based solution is that the URLs are the same for all pages, regardless of language. The server translates the pages based on the cookie, and if there is no cookie, the default language is used. Search engines cannot use cookies because they cannot target cookie contents when linking to sites, so they only index the default language. So instead, by specifying the language in the URLs, all the translated pages will be uniquely addressable, and will thus be indexed by the search engines.
From the end users' perspective, they will be able to search in their own language and get the relevant hits, instead of having to search in English only. They will also, like the search engines, be able to link to widgets.opera.com targeting specific languages.
Currently, the benefits apply mostly to our Japanese speaking users, but if we were to add support for more languages at a later stage, the same benefits would apply to them.
How we implemented itIn this section we present the technical details of our language solution. This is hopefully of interest and help to others with similar wishes and issues regarding internationalization. A previous blog post presented some of the technologies we use for the widgets.opera.com site. In short, widgets.opera.com is a Python WSGI application using Pylons with Genshi, FormEncode, and AuthKit.
Moving the language specifier to the URLs has involved updating various sources of URLs in the system to include the request language when necessary, but we will cover the whole internationalization solution for completeness. The specific points of interest are:
We will now go through each of them in turn.
Transparently extracting the language from the URLsBecause there are two types of URLs for almost every Pylons controller in widgets.opera.com (with and without a language specifier), we wanted to handle the language extraction at an early stage, so that only one type is matched against the routes mapping. We found that by extending the Routes Mapper and overriding the match and routematch methods, we could manipulate the the URL right before the matching takes place. Here we extract and remove the language code, and then add it to the match result, making it end up being available from environ['pylons.routes_dict'] in the base controller. From there we use it to set the language in Pylons. The custom mapper looks something like this:
from routes import Mapper
class LanguageDetectingMapper(Mapper):
def match(self, url):
url, lang = self.detect_language(url)
result = self._match(url)
if result[0]:
result[0]['_lang'] = lang
if self.debug:
return result[0], result[1], result[2]
if result[0]:
return result[0]
return None
def routematch(self, url):
# Similar to above
def detect_language(self, url):
# Detect and return the language code, if
# any, and return the URL without it.
The highlighted lines are the basic differences from the actual Mapper source code. Now that the language is set, we can start using it.
Various language enabled helper methodsBecause the language itself isn't present in the routes, the url_for method doesn't know about it, and thus had to be redefined to include the language prefix when necessary. And because of that, the redirect_to, current_url, current_page, link_to_unless_current methods, etc. had to be redifined too to use the custom url_for method. For some of these methods we kept a *_no_lang version to be used for language independent URLs and checks.
We don't do anything extraordinary when it comes to internationalizing the Genshi templates. We use a callback method to the template loader which adds a Genshi Translator filter to the templates, as suggested in the Genshi documentation. The Translator filter is set to use Pylons' ugettext method, so that all text messages are fetched through Pylons' i18n framework:
from genshi.filters.i18n import Translator
from pylons.i18n.translation import ugettext
def template_loaded(template):
template.filters.insert(0, Translator(ugettext))
See the Genshi documentation for clarifications.
Internationalizing FormEncode validation feedbackFormEncode is treated similarly as the Genshi templates. All messages are fetched through Pylons by supplying a state object to the validation decorators, which maps the _ (underscore) method to Pylons' ugettext:
from pylons.i18n.translation import ugettext
class PylonsFormEncodeState(object):
_ = staticmethod(ugettext)
@validate(..., state=PylonsFormEncodeState())
def foo():
# ...
This is based on Pylons issue #296, with the main difference being that we don't fall back to FormEncode's own translations. Using the conventional methods for extracting the default messages from the code and templates, the translation keys are the fallback messages when no translations are found. This presumes that the fallback language is English, which is the case for widgets.opera.com.
Language-enabled redirects to error pagesThe ErrorDocuments middleware, which translates status codes into error pages, has to be changed to redirect to different error page URLs depending on the request language. The ErrorDocuments middleware gets the URLs from the Pylons error_mapper method, so by placing a language-aware prefixing method between the error_mapper method and the ErrorDocuments middleware, the problem is solved.
The relevant documentation can be found here.
Language-enabled redirects by AuthKit to the login pageWe have been using the redirect method in AuthKit for handling the situations where the user needs to log in in order to proceed. The only issue is that the redirect handler takes a static URL to the login page, while we need different URLs, depending on the selected language. So we replaced it with new redirect handler which can take a callable as well. Then we set the handler to use (our language aware) url_for as the callable with the route to the login form action as argument. The code for the handler is as follows, and is completely generic:
class CallableRedirectHandler(object):
def __init__(self, app, target, kwargs):
self.app = app
self.target = target
self.kwargs = kwargs
def __call__(self, environ, start_response):
if callable(self.target):
location = self.target(**self.kwargs)
else:
location = self.target
start_response('302 FOUND', [
('Location', location),
('Content-Type', 'text/plain'),
('Content-Length', '0'),
])
return ['Redirecting to %s' % location]
def make_callableredirect_handler(
app,
auth_conf,
app_conf=None,
global_conf=None,
prefix='authkit.callableredirect.'
):
if not auth_conf.has_key('login'):
raise AuthKitConfigError('No %slogin key specified' % prefix)
if 'loginkwargs' in auth_conf:
kwargs = auth_conf['loginkwargs']
else:
kwargs = {}
app = MultiHandler(app)
app.add_method('callableredirect', CallableRedirectHandler, target=auth_conf['login'], kwargs=kwargs)
app.add_checker('callableredirect', status_checker)
return app
The login and loginkwargs keywords specify the static URL or callable, and the arguments to the callable.
See the AuthKit Cookbook on how to create custom handlers.
That's itThat concludes the internationalization solution on widgets.opera.com. We hope this is of help to you or someone else in the future. If you have any thoughts or questions, feel free to write a comment.
It's a been a while since we unleashed widgets.opera.com 2.0 on the world so we're long overdue a blog post from the devs regarding what we have done and where we intend to take things.
Why change?With implementations of Opera Widgets for devices and mobile phones developing fast, we knew we needed to evolve the site in order to keep up with new requirements as well as improving the experience for both users and widget developers. Below I will explain what we've achieved so far, and what is still yet to come.
Progress so farThe code under the hood has been completely rewritten, plus the following improvements have been made, to make widgets.opera.com better for the users and developers of the Opera Widgets community.
For those more interested in the technical details here are a couple of quick snippets of information. I hope to blog about some of the programming topics in more detail over coming weeks.
Hardware 2 blade servers both with two 2GHz Intel Xeons and 4Gb RAM Workload approximately 3,500,000 total requests per day (including media) Language Python Application Framework Pylons Templating Language Genshi Database MySQL What's coming next?Now that we have the new code base in a kind of stable state, what are we working on? From now on, you can expect to see us start on some of the more advanced features aimed at making widgets.opera.com a more tailored experience for the user.
FeedbackIf you have any feedback regarding features or bugs with the site then please raise it on the widgets forum. We do check up there regularly and try to provide a response whenever we can.
public class ServerSentEvent implements Serializable
{
/** DOM 3 Event namespace */
private static final String DOM3_EVENTS_NAMESPACE =
"http://www.w3.org/2001/xml-events";
private String type;
private String target;
private Boolean bubbles;
private Boolean cancelable;
private String namespace;
// *snip * The usual getters and setters
protected void generateEventContent(StringBuffer sb) {}
public final String toString()
{
// Provide default event properties
StringBuffer sb = new StringBuffer("");
sb.append("Event: ").append(type).append("\n");
sb.append("Class: ").append(this.getClass().getSimpleName()).append(
"\n");
if (target != null)
{
sb.append("Target: ").append(target).append("\n");
}
if (bubbles != null)
{
sb.append("Bubbles: ")
.append(bubbles.booleanValue() ? "Yes" : "No").append("\n");
}
if (cancelable != null)
{
sb.append("Cancelable: ").append(
cancelable.booleanValue() ? "Yes" : "No").append("\n");
}
if (namespace != null)
{
sb.append("Namespace: ").append(namespace).append("\n");
}
// Provide method for subclasses to provide more event info
this.generateEventContent(sb);
// Terminate the event with a blank line
sb.append("\n");
return sb.toString();
}
}
public class RemoteEvent extends ServerSentEvent
{
/** Namespace for remote events */
private static final String REMOTE_EVENT_NAMESPACE =
"uuid:755e2d2d-a836-4539-83f4-16b51156341f";
private List<String> data = new ArrayList<String>();
public RemoteEvent(String type)
{
super(type, REMOTE_EVENT_NAMESPACE);
}
public void addData(String data)
{
this.data.add(data);
}
protected void generateEventContent(StringBuffer sb)
{
super.generateEventContent(sb);
for (String currentData : data)
{
sb.append("data: ").append(currentData).append("\n");
}
}
}
There are also a couple of interfaces defined for listening to and generatingserver sent events in a similar manner to how such event listeners are handled in swing.public class SSEClient implements ServerSentEventListener
{
public SSEClient(HttpServletRequest request, HttpServletResponse response)
throws IOException
{
continuation = ContinuationSupport.getContinuation(request, this);
this.response = response;
this.request = request;
writeHeaders(this.response);
}
private void writeHeaders(HttpServletResponse response) throws IOException
{
// Set the necessary headers
response.setContentType("application/x-dom-event-stream");
response.setCharacterEncoding("utf-8");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Transfer-Encoding", "Chunked");
response.flushBuffer();
}
private void writeEvent(ServerSentEvent event) throws IOException
{
byte[] eventBytes = event.toString().getBytes();
ServletOutputStream os = response.getOutputStream();
os.println(Integer.toHexString(eventBytes.length));
os.println(event.toString());
os.println();
os.flush();
response.flushBuffer();
}
public void run()
{
try
{
// This outer while loop ensures correct operation when emulating
// continuations i.e. not running in Jetty
while (true)
{
synchronized (this)
{
while (events.peek() != null)
{
log.debug("Found an event, writing it to the stream");
writeEvent(events.remove());
}
}
continuation.suspend(WAIT_TIME);
}
}
catch (IOException e)
{
this.disconnect();
}
}
}Finally we need a service class to manage the currently connected clients that can be called on by servlet code. This is so simple I'll let you poke at that in the download.$ mvn jetty:runJavaSSE.zip
$ java -XX:MaxPermSize=128m -Xmx3072M -jar start.jarThen on the Apache bench machine I set up a bunch of requests with a command such as
$ ab -n 50000 -c 2000 -t 18000 http://jettyserver:8080/JavaSSE/ServerTime
Some folks have been curious about a little project we cooked up over on Opera Labs: The <video> element.
You may have seen we're approaching the 1001st widget on widgets.opera.com. 1001 widgets is a milestone we want to celebrate properly with the readers of this blog. So we invited a special guest to our Web chat next week.