Mac OS • 45:40
Mac OS X makes it dramatically easier to localize your products. Learn how to work with application packages to take advantage of the powerful, modern framework for localizing your application.
Speaker: Chris Hansten
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Welcome to session 157 on Mac OS X and localization. I think it's probably about that time on this Thursday afternoon where you're all thinking of going to the beer bash, so we'll try to make this short and sweet. My name is Chris Hansten. I work in the localization and integration group here at Apple.
In just a couple of minutes, we're going to bring Mike up here, one of our engineers, and he's going to discuss some of the APIs that we've developed and how to make your software work well for localization. But before we do that, I want to say just a couple of words on why we think localization is actually a very strategic thing and something you should consider for all of your products.
So, now Apple has always considered localization very strategic and very important to our business. And that first line up there on that slide says all you really need to hear about why that is. It makes it very obvious. Approximately one half of Apple's computers are sold outside the U.S. That's 50% of course of the market for your applications as well.
So, now there's no way we can position ourselves in the market this way and sell all these Macs overseas without significant investment in high quality localization of our system software. In fact, this is so important to us that we do, we go to great lengths to deliver our operating system software and CPU software virtually simultaneously with the U.S. product. Now, with Mac OS X, we want to do this even better. And we've put some capabilities in our operating system that should make this easier both for us and for you.
[Transcript missing]
For example, multiple localizations can now be packaged and included in a single product, which cuts down significantly on the overhead in delivering localized product. As well, alternatively, a language add-on for your app can be easily packaged and delivered as, say, a web download. And this allows you some flexibility in doing your scheduling around localization and scheduling, you know, vis-a-vis your US product. So these sorts of things are possibilities generated by these APIs and these capabilities in the OS.
Now, as you can see, we're very excited ourselves to use these capabilities within our own products. And we want to encourage all of you to do that as well in your products. So to do that, let's get straight to the fun stuff and have Mike come up here and give some demonstrations and describe these technologies to you. So, Mike.
Chris Hansen: Is my mic live? Okay, there it is. I want to talk to you just a bit about localization on OS X. And what we're going to cover is basically what you need to be thinking of to produce an application that's localizable, and then how the core foundation frameworks help you in your code to deal with these issues and to build an application which is localizable. And then finally, we're going to look a little bit about how Project Builder supports this in the development cycle.
First of all, let's look a little bit about delivering localized applications. The old way, a separate application for each language. You have to have multiple SKUs, multiple boxes, packaging, all this stuff. And you also then, if you want to have multiple languages supported, have to have multiple copies of that app on the system. So it's not very good for a multi-user situation where you might have some people using your application who want to run it in one language and some other people who want to run it in another.
What we want to move towards is one of the two second possibilities here. Either you simultaneously ship your application that contains all of the localizations together. Now this would be ideal, but we recognize that it's not always totally practical. Not everybody can spend the effort to actually produce all the localizations before they actually can sell one of them. We don't force you to do this, but it's totally possible.
The other option is you just ship localizations as you finish them after you ship the main app, but all they are is add-ons. It's not a whole other copy of the application, and the installation ends up merging the new localizations into the app that the user already has. Now, what I want to demo first, if we can get the demo machine up on the display, is just what it means for an application to be multiply localized.
You've probably seen this panel in a few other talks. This is the International Preferences panel. And this list here is basically, for me, the person who's logged into this machine, what's the preferred ordering of languages for me? And, okay, so I might want to see English, you know, primarily.
And if I launch an application, you know, we can see that it comes up in English. Okay, easy enough. You've probably seen that. But what if I reordered this list and say that I'd like to see French first if it's available? Well, now, you know, it comes up in French when I launch this application.
These preferences are per user, so if you have multiple users that use the machine, they all get their own list of ordered languages. Somebody might want to see Japanese instead. The app launches in Japanese. This is the same app recognized, right? But it's just that depending on what my preferences are, it's going to pick the localization that's appropriate. All right, so that's the end of the first demo here. We can go back to the slides.
So, all right, first of all, what does it mean to be localizable? Localizable is different from localized, right? Localizable means that it's possible to localize this application, right? The developer can easily produce a localizable application. You know, they don't have to speak any languages other than their native language. They can still produce an application that can be translated. And that's what you should be shooting for as a developer.
You should build an application that could be translated, even if you don't plan to do it right now. Because later, you might want to do that. And if it's localizable, it'll be very easy to do that. You won't have to rev your code at all. You just have to send your resources off to some translator, who will then translate it into the target language, send it back to you, you put it in the app, and it all just works.
What the developer has to be aware of is not necessarily issues specific to any language or region, but just general issues that have to do with localizability. So you want to have your code deal with strings in a way that's flexible enough that it's going to work for strings in any language.
Carstar is no good, right? You want to use something else. We'll get into how that is supported in Core Foundation. You want to look at the same issue with regard to file formats. Don't use ASCII in your file format if you can help it. Use something that can represent more languages.
You want to deal with text input issues. So not all languages are as simple and easy to type as English. Typing Japanese can be very complex because there are so many different characters and you're not going to have a keyboard with 50,000 characters on it. So you have to have some sort of input method that lets users type in arbitrarily complex languages.
You also have to be aware of things like differing formats for dates and numbers and so on and so forth, and even potentially differing formats of various hardware standards that are available in different areas of the world. And then finally, and what people always think about sort of first when you think about localization is, you have to make sure that all of your interface and all of the other resources that might be different for different languages can be.
Let's look at how Core Foundation actually helps you to achieve this. We won't go through all of the things that I just listed, but we'll cover most of them here. Some of the main classes, I'll call them, even though they're not really in core foundation, which help you with this sort of thing are CFString and CFCharacterSet. Those help you deal with strings in a way that will not limit you to a particular script.
CF Property List and CF XML Parser. These are options for file formats that will be totally internationalizable. And then CFBundle, which is really a very central piece of the system on Mac OS X. And in addition to all the other things that it does, it helps make sure that you're going to get the right versions of your localizable resources when the user runs your application. If you use CFBundle properly, you won't have to worry about the user's preferred language order. It'll all just work.
So what about strings? CFString should be used as much as you possibly can in your app. Use it instead of Carstar everywhere in your internal representations. Avoid converting it to Carstars or Pascal strings whenever you can. CFStrings are Unicode strings. So Unicode can represent text in almost any language in the world, right? Maybe any language. No, OK, almost any language at least. And when you do have to actually deal with other character encodings, CsString also provides conversion facilities.
So, okay, here are a few tips for dealing with strings. First of all, the Unicode spec is fairly complicated, and to correctly handle Unicode strings, there are a lot of details. Unless this is your domain of expertise, you probably don't want to get involved in these details. So let CFString do it.
Avoid going through a string character by character. In Unicode, there's almost always an infinite number of ways that basically the same semantic content could be represented in a string. There's different forms you can use that basically don't go through character by character doing comparisons because it's not going to necessarily be the right thing.
So use built-in character sets. The CF Character Set class allows you to test for membership in certain sets. There are a bunch of predefined sets. So you can say, is this particular character a letter? Is it a number? Is it white space punctuation? Is this a diacritic mark? There's all kinds of built-in sets. So if you need to figure out for a particular character sort of what class this character is in, you can use CF Character Set.
You can also use the built-in support for collation and comparison. So to do locale correct sorting or string comparison. Remember I said that there's various different ways that you could represent the same string in Unicode. If you compare them byte for byte, two strings that are really the same semantic content might look different. If you use the comparison functions in Core Foundation, they should look the same. And also, if you're looking for substrings, this is sort of a sub-point of the whole comparison and collation thing. If you're looking for substrings within a bigger string, use the APIs in Core Foundation.
Now, unfortunately, not every API in the world is based on CFString. Not even every API in Mac OS X currently has CFString APIs to deal with it. So sometimes you're going to have to convert to some other encoding. CS String provides the facility to convert back and forth between CarStars or Star 255s and CS Strings.
But this is an area where you're going to have to kind of pay attention, right? Not every encoding can support all of the characters in Unicode. If, you know, ASCII could support all the characters in Unicode, you wouldn't need Unicode. So if you're going to be converting to some encoding, you have to be aware that a particular CFString might have stuff in it that just cannot be converted to a particular encoding. In areas where you have to do these conversions, just make sure that you're aware of these issues.
So here's a few selected methods for CFString and CFCharacterSet. You can create a string from bytes of a particular encoding. So that's the way that if you had a car star that you knew was in a particular encoding, or maybe a star 255 or whatever, you can create a CFString from that, converting the string, whatever encoding it's in, into the internal representation.
Now, it's not always going to blow the thing up into full Unicode, but conceptually, you can think of it that way. The opposite of that is CFStringGetBytes. You can use that to extract a representation of a CFString in a given encoding. And this is one because not all encodings can represent all strings that might fail, and you have various options to figure that out.
You can compare strings with CFString compare with options. You can find substrings with CFString find with options. And then I mentioned character sets a little bit earlier. You can get the predefined ones using CFCharacterSetGetPredefined. There are a bunch of constants in the header file for that class which basically list the various predefined sets you can ask for. And then once you have a character set, you can test whether any individual character is a member of that set with the CF character set is character member.
File formats. Again, the same sort of issues with strings. You don't want to pick a file format that's not rich enough to express international content. Don't tie yourself to limited string encodings. While I'm mentioning it, don't tie yourself to a specific platform. This is mostly sort of endian-ness issues. Make sure that you take that into account as well.
Use a standard format if it's appropriate. So XML is a standard format that's a particularly good choice here because XML is either stored in Unicode or in UTF-8, which is another representation that is just as rich as Unicode. Core Foundation has support for dealing with XML. For simple and small sets of data, you can use CF Property List, an extremely simple API for serializing and deserializing graphs of data objects like dictionaries and arrays and strings. For more complex stuff, you can use the CF XML Parser, which is a full XML parser that you can use to write XML of whatever format you want.
So here's a couple of examples. The property list stuff is very simple. You just hand it a data object, and it'll give you back the original graph of objects that you used to create it in the first place. You can then take that graph of objects and hand it to CF Property List Create XML, and it'll hand you back a data with the XML in it. The parser, these are the entry points for the simple entry points for the XML parser. There's lots of options. There's also a more complex API. We're not going to get into that. But just so you know, Core Foundation does have support for XML parsing.
This doesn't really relate to Core Foundation so much, but it's another big issue. You need to deal with text input. The way that you deal with text input is actually a little bit different for the Carbon and the Cocoa stack. In Carbon applications, all the controls are pretty much ready to go. So if you're using appearance controls for those things, you should be fine.
For larger text, Instead of using the older TextEdit Manager, consider using the multilingual TextEdit Manager, which is Unicode-based and can deal with these things, and it also can have more than 32K of text in it. And if you're writing your own text system, you can integrate with the Text Services Manager, which is basically the sort of lower-level input management API for Carbon.
On the Cocoa side, well, the Cocoa text system is used for all of the text drawing in Cocoa. And the Cocoa text system is fully Unicode aware. And it also, it automatically integrates with the text services manager to do input management. And so basically for 99% of your Cocoa needs, just use the text system. If you're writing your own text system for whatever reason, you can implement the text input protocols. Those automatically integrate with TSM and they allow you to, if you're doing your own text entry management, take full advantage of the input management facilities.
Okay. So here's where we're going to spend a fair amount of time, is talking about resources and bundles. Resources. Well, OK, the term is a little bit overloaded these days. Resources, when we talk about resources on Mac OS X, there's kind of two kinds. There's bundle resources, which are just basically files that live inside of a bundle, and there's resource manager resources, which are the traditional sort of Mac OS 9 resources. CFBundle provides a way to deal with both of these things.
And it also provides the structure for dealing with localization. And it knows the proper way to search for resources that may be locale-specific. Inside of a bundle, The localized resources are segregated into their own areas, what we call lproj folders. We'll actually see some of that in a moment. I want to talk a little bit about the difference between regions and languages. A region actually includes both a language and a location.
There's the English that we speak here in the United States. There's the English that they speak in England. There's the French that is spoken in France, and there's the French that's spoken in Canada. We can localize by region, so you can actually provide different variants for French-Canadian and for France-French.
Or you can just do it by language. Or you can do it by a combination. So let's say that for the most part, all your resources are the same, whether people are in Canada or in France, but there's a small subset that actually have to be specific. You can put most of them into language localization areas, but put just the small set into the regional areas, and we'll take care of all that for you.
Oh, I should mention, the region names are actually the ISO standard region names, so like EN underbar US and so on and so forth. Language names, mostly for compatibility reasons, are OpenSTEP style language names, which are typically English versions of language names like English, French, Japanese. So it's German, not Deutsch, even though if you were going to display it in the UI, it would be Deutsch.
I'm not going to go through this slide in any great detail. You can find this picture in the system overview manual if you want to go through it in detail. My point for showing this slide is just to impress upon you that you're better off letting CFBundle find the resources for you.
Because otherwise, these are the places that you have to look and the steps you have to go through to make sure that you're going to get the right version for the user's preferences. So I don't know if this is readable at all from back there, but basically we're going to go off and look in all the appropriate places for these resources, and the first place we find it, that's the one that we're going to use.
I spoke a little bit about the difference between resource manager resources and bundle resources. Just to hit on that again, the resource manager resources are the ones that are typically found in the resource fork of an application on Mac OS 9. Bundle resources, on the other hand, are just files that go inside your app wrapper.
And as I mentioned, CFBundle deals with both of these, and both of them can be either global or localized. So resource manager resources, we're trying to get away from relying on the resource fork because we want to be volume format independent. And also, there's only one resource fork in your application, but we need several places to be able to store these resources so that you can have multiple localizations.
So instead, we put resource manager resources into the data fork. It's the same format that you used to find in the resource fork. It's just in the data fork of separate files that go inside the app wrapper. The resource manager resources are actually stored in files, which are themselves bundle resources of the bundle.
We separate out the resource manager resources that don't need to be localized from the ones that do, so you don't have to ship multiple copies of the stuff that doesn't change, only multiple copies of the stuff that does change. Now, CFBundle has API to open the resource manager resource forks and put them onto the chain, the resource chain in Carbon. And it correctly deals with opening both the global and the localized resources. For the localized ones, it correctly deals with finding the right version for this user's preferences.
Carbon automatically calls this API when your app launches for your application, so you don't have to worry about that. Just like on Mac OS 9, your resource fork was already open for you when your app started. For frameworks or for plugins, you can use this bundle API to cause the frameworks or the plugins resource manager resources to be added to the Carbon resource chain.
Bundle resources, again, just files inside the bundle. The API on CFBundle, basically, you ask for a resource by name, we give you back the URL of that resource where it resides inside the bundle. Don't go groping around inside the bundle to try to find them yourselves. Because again, that picture is very complex.
Once you've found the URL that locates this resource inside the bundle, you use whatever API is appropriate for the kind of file that you're looking for. So, you know, if it's a TIFF file, you're going to go off and use a TIFF library to open up that image and parse it. If it's, you know, an RTF file, you're going to go and tell, you know, some RTF editor to open it or whatever.
Now, there's one kind of bundle resource that's handled kind of specially by CFBundle, and that's strings files. And I want to kind of-- You can spend a little time talking about strings files because for the Carbon folks in the room, this is, I think, one of the first kind of bundle resources that you might want to look at moving some of your resource manager resources to use instead.
If you're using Resource Manager resources to do string localization, I guess there's the STR number resources and so forth. Okay, fine, but those are not necessarily the easiest things in the world to maintain, and you have to compile them with res and all this other stuff. Strings files provide a nice, easy way to do that same thing. And so I anticipate that Carbon developers, if they want to get started using some bundle resources instead, this might be the first thing that you're going to do.
Any user-visible strings obviously should be localizable. And strings files are basically key-value pairs stored in Unicode in a plain text file-- well, a plain Unicode text file-- inside your bundle. And CFBundle actually has specific API that you can use to locate these strings files, open them up, cache their contents, fetch strings out of them for the particular localization that's appropriate.
The developer tools also have support for helping you to even generate these strings files just from your code. So there's a command line tool called genstrings in DP4. This is not integrated into the project builder bank process, but it will be eventually. And what this tool does is it just sort of goes through all your code, looking for places where you're looking up localized strings, and then it spits out skeleton strings files that contain all the keys that you're ever going to look for in your code. you go in and just fill in the values.
Oh, you know what? One more thing I want to mention with the strings files. CFString has sort of printf-like APIs so you can use format strings and format arguments into them. With strings files, we actually have support, and with CFString itself, we have support for argument reordering. So if you have two arguments that in English are in this order, but in Japanese they need to be in this order, no problem. That can all be done.
All right, so here's some of the API for CFBundle. For actually locating bundles, you can easily get hold of the bundle that contains the application that's running with CFBundle Get Main Bundle. If you're a framework or a plugin and you want to locate the bundle for that framework or that plugin, you can use CFBundle, get bundle with identifier. You can basically assign a unique identifier to each bundle.
And then if in that bundle's code, you can look it up by that identifier. You can make sure that you get the right bundle. You don't have to know where it lives on the disk or anything as long as your code is running. You know that it's loaded into the app and you'll be able to find that bundle by its identifier.
The Info.plist of a bundle contains a bunch of metadata. We're not going to talk too much about that, but you can use CFBundle.get.info dictionary to get the whole Info.plist dictionary. Some of the values in the Info.plist dictionary actually end up being localized a lot of the time, and they get localized in a separate file called Info.plist.strings.
You can use CFBundle.get.value.for.info dictionary key to look up specific values from the info dictionary. That API will actually look into localized stuff first. If you have, say, a CFBundle.name key, which is what Carbon uses to display the name of the application in the menu bar, You want that to be localized, obviously, so you can use CFBundle.getValue for info dictionary key to get that string and make sure that it gets it out of the localized data if it's there, otherwise out of the global data.
You can use CFBundleCopyResourceURL to find bundle resources inside the bundle. Again, you just give it the name. We'll go out and look for it inside the bundle and return you the appropriate one. Open Bundle Resource Files is the one that you can use in a framework or a plugin to open up the Resource Manager resource files inside of a bundle. CF Copy Localize String is one of these functions that we have for specifically dealing with these strings files. You just give it a key and it will go find the strings file, look that key up, and return it to you.
And these localized strings functions are also the thing that that GenStrings tool looks for when it's automatically generating strings files for you. Okay, next demo. So we get the demo machine back up on the screen. I want to show just briefly, and I know that this has been shown a couple times before, so bear with me if you've seen it, what the insides of the bundle looks like.
We have a little secret shortcut here to actually go inside of a bundle, which is usually shown opaque in the finder. Let's put that in list mode. The only thing that's in the top level of a bundle is typically this contents directory. Inside that, we have the info P list, which again is this metadata about the bundle. There's been more info on that in other sessions, so I'm not going to go into that. And the package info, which again, you may have heard some about, but we won't be talking about.
The actual binary for the application lives inside of a folder named after the platform it's supposed to run on. You might have another folder in here called Mac OS Classic, which would contain the binary for Mac OS 9 if you had a separate binary for Mac OS 9. Then what we're really interested in here is the resources directory.
All the plain files that you see in this directory, those are the global resources, the ones that don't need to be localized. I don't need to localize the icon file for this application. It doesn't have any text in it, and it doesn't need to be localized, so it sits here. One that deserves special mention, perhaps, is this guy. This is the DataFork file that contains the resource manager resources for this application, which are global. So these are the ones that don't need to be localized.
Now all the localized resources go inside these Lproj directories that I talked about a little bit earlier. You can see that we have four localizations here, one for English, one for French, one for German, and one for Japanese. The contents of these directories is always identical, or should always be identical. Basically, you need a copy of each and every one of these localized resources for each language or region that you're localizing for.
So we have the credits file in French, and we have the credits file in English, and we have all the various interface builder files. We have a standard strings file that is going to be localized. This one is the resource manager resources for this application, the localized ones. So you notice we have a copy in here, and we have a copy in here. And again, when you use the bundle APIs, the appropriate one is going to get opened and put onto your resource chain.
So, okay, pretty simple. And, uh... Oh, the localized plist would also go into the lproj folders in a file called Info.plist.strings, I believe. So that's just another strings file, but it has a special name so that we know that it's the one for the plist. And it just goes in the lproj with the rest of the localized resources. OK, that's the end of this demo.
You can see what the structure of the bundle is. You should never have to mess with that. Project Builder knows how to build bundles, and we're going to get into that right now. So Project Builder, the new development environment for Mac OS X, has full support for building bundles, for building multiply localized applications, for managing all the details that you need to have taken care of when you're dealing with multiply localized Mac OS X bundles.
So we know how to build the bundle disk structure. You shouldn't involve yourself in that if you can avoid it at all because, well, you know, we know how to do that and you shouldn't necessarily have to also know how to do that. We let you localize the InfoP-List data. We let you localize your bundle resources. We let you localize your resource manager resources, all of that.
So Project Builder has target types that represent applications, frameworks, and bundles or loadable plug-ins. All of these basically end up building bundles of one flavor or another. Each of these targets basically constructs the bundle automatically as it builds. And the build system knows where to put all the various pieces. Then, as I think I had mentioned, actually, Project Builder has full control for managing what's in your info P list as well.
For the resources, the target model in Project Builder is that we have these build phases. And so, you know, we're going to go through and for the target we build each build phase. And so there's build phases that will compile your source code. There's build phases for frameworks that will copy your headers into the frameworks header directory. There's build phases that will copy your bundle resources into the bundle.
There's a build phase that will run res or res merge on all your .r or your .rsrc files and put those into the bundle. And so this is how Project Builder deals with building things. And the build phases that do bundle resources and resource manager resources know about localization. X.
Now within the IDE, we also help to enforce some of the requirements for CFBundles. One of those requirements is that A resource has to be either global or localizable. You shouldn't have a file in your Lproj directory and also have it at the top level of the resources.
If you do, the global one is the only one that will be found. That seems a little backwards. The reason that's done is for performance. You're not supposed to have them both. If you do have them both, the first one we look for is the global one, because we only have to look in one place for that.
We also make sure that whatever the development language of your project is, you have to have a full set of resources for those. You should have a full set of resources for any other regions that you support, but if you don't, for a particular resource, we'll fall back on the development region version. And that's why you have to have a development region version. That means that we'll always be able to find at least one version of this resource.
And then for the platform-specific stuff, we require that there be a generic one if you have any platform-specific ones. And that's just in case, say, another platform was to be supported at a later date. If you only had platform-specific ones, which one do we use, right? You didn't anticipate the new platform. If there's a generic one in there, though, we can use that.
So, all right, let's actually look at how this gets done in Project Builder. Now, what Slightly modified version of the SimpleText project that ships as an example on OS X. And when I say slightly modified, what I've done is I've made SimpleText localizable already. So SimpleText wasn't quite fully set up to be localizable, but I've tweaked it around a little, so it is. We can see all the .R files that are in the project. These are the things that are going to go through REST and build the resource manager resources that SimpleTax needs. Now, first let's go ahead and build this.
While that's building, maybe I'll point out that I've also sort of broken up the .R files a little further than they are in the default simple text. I've actually taken out and put in a separate file all of the stuff.
[Transcript missing]
All the R files were merged together and put into one .rsrc file in the data fork. All right, let's prove that this actually works.
So here's simple text. I can bring up the Find panel and so on and so forth. OK, fine. SimpleText has a bug, yes. Now, OK, so I have this application now that's localizable, but it's not localized, right? How do I localize it? Well, these are the files, again, that need to be localized. This one, the global stuff file, this one does not need to be localized. But this one, these all do. So what I'm going to do is I'm going to go to the project inspector.
The inspector can act on multiple selections, so you can do all this stuff in a batch. And there's this localization and platforms pull-down menu. And all you really have to do here is say, "Okay, I'd like all these to be localized, please." You'll notice that they all got disclosure triangles.
And what that shows, if you disclose it, it shows a list of all of the different variants that we have for this localized resource. And so when we first make it localized, we just get English, which is the development region for this project. And that's what we start out with. But the whole point here is we want to actually localize it. So once we've made these localizable resources, we can go ahead and add a new variant. So let's say add one for French.
You'll notice that for the ones I disclosed, it added the French version here. Initially, when you add a new region variant, it starts out as a copy of the development region. So if I open up this file, which actually seems to contain a lot of the strings, we'll see that the French version is in English, right? Because it just copied the English file.
I'm not going to make you sit here and watch me, my poor command of French here. I actually had a colleague localize this one for me, and I have this little cheater file. We'll go ahead and just copy the contents out of there and go back to the French version of localize.r, and we'll just replace it. Save that. Now we have this version, which if I go up to part that actually was localized, you can see it has been localized into French.
Let's build that. Now, it didn't build any of the code because we already built that. Now, if you notice though, now it's running res on some of these files and it's putting them into the localized.rsrc of the English.lproj. It's doing some more of them for the French.lproj, right, because we have French versions for everything now. And then finally, it's doing the global one for the one that we didn't localize.
Okay. And now we can run. Hopefully it will still come up in English because I haven't changed my language preference. Ooh. You know what, though? I forgot to change my preference back to English after the first demo. So, all right, well, here it is in French. All right, let's do this in the right order, though. All right, so. We can run this and it'll come up in English.
Actually, you'll notice that before I had left the preference order Japanese, French, English, right? We hadn't localized it into Japanese, but we had localized it into French, so it showed up in French, even though I would have preferred Japanese if there had been one. Okay. So here it is in English, right? We still have the find panel. Okay, fine. But if we go back here and we say, OK, well, let's make that French instead and run it again.
Okay, here it comes in French. And you'll see that in this case, just doing the strings isn't going to quite be enough. We'll probably have to go in and tweak some of the DITL resources to change some of the control sizes when we want to make this actually fully localized into French. But you get the idea, right? So that's as easy, you know, as it is to actually support this stuff in Project Builder. And that is the end of that demo.
So that pretty much concludes the content here. All of my pointers point backwards. All of these sessions have already taken place. If you saw them, good for you. If you didn't, well, OK. Maybe some of these slides or even the video for these presentations will be available at some point. And if they are, you might take a peek at some of them.
Core Foundation is a wonderful technology for dealing with these localization issues as well as a lot of other stuff. So definitely check out the Core Foundation stuff if you want to learn about Project Builder. There's the Project Builder sessions. There's the application packaging and document typing talk that Arnaud gave earlier this afternoon that deals with other bundle issues.
There's the Apple Localization Tools talk, which was in this room right before this talk. And then the one that isn't already over, there's the Texan International Feedback session tomorrow morning. Tim Holmes is the one to contact about this technology. His email address is there. And now I'll invite a few people to come up. Hopefully they showed up for Q&A.