There is a strong stream of support for using the Drupal built in t() function to translate taxonomy terms, vocabulary names, menu items, profile field titles and options, poll titles and options and similar user specified content. This is a very bad practice, and should be changed. It should be pointed out that this system is reused, because it is ready and seems to be easy and fitting for the problem. Unfortunately it is neither easy, nor fitting. Let me explain why, and where should we look for a solution.
What t() is designed to achieve?
Drupal has a built in interface translation mechanism. Literal strings marked with t() in the module and theme source codes are extracted when a new Drupal version is released and translation template (POT) files are generated. These templates have all literal strings provided by the Drupal source ready for translation. If some user turns on the locale module, she can import any of these translated templates (PO files), either mixing them under one language (core translation, contrib module translations, theme translation) or adding them to different language (Italian Drupal core translation, German Drupal core translation, etc). It is possible to edit these translations on the web interface, search for untranslated strings and import/export translations as PO files.
There are some fundamental approaches in this solution, which result in it being quite bad for user specified string (menu item names, taxonomy term and vocabulary names, etc) translation. The t() mechanism is used regardless of these problems in both i18n module and localizer module (plus some other modules in contrib, which are not inherently i18n related, but itend to be kind to their users with such extra features). Let's use a menu item as an example to simplify the text further on.
t() collects strings dynamically
If you add a menu item, you first need to click to see at least some other interface language, so that t() can collect the menu item string. It is also important that you actually see that menu item displayed on the page, since this is only when t() is called.
» A possible solution is to inject the menu item string into the locale table when adding the menu item. This approach is not used for some reason by any of the t() using modules.
The locale module interface is not user friendly
The locale module interface is anything but user friendly when it comes to translating strings. Some of you might remember the good old days when that was the only option (apart from editing SQL dumps). At that time, the Drupal interface translations were very rare. When with the active involvement of some experts in thsi field (most notably Jacobo Tarrio), we introduced PO import functionality and POT generation (see above), the number of translations skyrocketed. Using a desktop tool which has much better support for your workflow was a lot easier. The locale module interface did not change since then, it is still not useable.
Consider the workflow of adding that menu item, switching your language to some other interface to get that menu item into the locale table, then going to the locale module admin page, go to the strings tab, search for untranslated strings, get that menu item and translate it to different languages. Repeat this for more menu items. This isn't what our users are dreaming about. Sure t() is easy on the programmer, but is it easy on the user? No.
» Sure, we can solve this by injecting more input fields for each language into the menu item addition page (see tr.module screenshots for nice examples). This is how path aliases were worked into the node submission page for example. You need the functionality there? Provide it there!
You mix the pretranslated stuff with user defined strings
One of the bigger problems of using the t() mechanism to translate user specified strings is that there is no way to distinguish them later on from the PO imported strings. When you export your translations, you either get all interface and user defined translations, or nothing. This is especially a problem, when building a new site. You have some Drupal 5.0 beta for which you have a work in progress translation you grabbed from somewhere. You add your menu items, then after some week you start to deploy the site. You need the final translations online, but your menu item translations should also be there. No way, unless you can live with unused translated stuff in the database. Now unused, but previously t()-ed strings are not possible to detect, since the t() collection is dynamic (see above).
» OK, you say we should identify user specified strings in the database specially. It is certainly possible, since there is already a comment field for strings (which contain file names and line numbers for PO imported strings). We can extend the import/export interface on this, so that you can import and export user specified strings. This is already done in locale module, so this seems to be a perfect fit. Unfortunately it is not. Remember that PO file source strings are English! See below.
t() collects English(!) strings
The concept behind t() is that you write your module/theme/.info file source in English, and apply t() to literal English strings. The primary language of Drupal is English. If you add a menu item, you need to add it in English, even if you don't have a publicly visible English interface (because you only provide French and Dutch interface for example). Even if you add a menu item on the French admin interface, you need to provide it in English, so that it can be translated to other supported languages. Note that locale module explicitly asks you to provide the language name in English, when you add a new language by hand, exactly because of this reason. (Drupal has most languages generated into the base translation templates, so when you import a translation, your English language names will get translated to their equivalents in different language interfaces, this is why).
It is popular to abuse this system, if you don't have a public English interface. You can provide the menu item text in any language you wish (eg. French). When you provide translations, retype the text in the French field, so that it gets recorded there, and will not show up as untranslated later. It is not too wise to tell people that they need to provide the menu item text twice to use it. Also it is very confusing if different admins add menu items in different languages.
» How could we solve this? The Drupal design is very strongly built in this direction (ie. you can't have a translation without an English original). See we can solve all the above problems, we can build a custom interface on top of t() to better support our users, but no nice interface helps to use some system for something which it was not intended to be used for.
All right, what should we do then?
Good question. You see that t() is not the right tool for the job now. Some of it's problems could be solved by an additional interface (think the forum setup interface, which is a simplified taxonomy interface). But to be useful for our purposes, we need to rethink its base design, or we need to introduce a similar solution specifically designed for user specified text translation.
The locale storage design is architected for quick translation retrieval. It used to be a (source string, translated string) connection table, but it quickly turned out that this way we need to store source strings multiple times for each langugae. This is why we have a locales_source table which stores the source string with an ID (expecting that is in English) and a locales_target table which provides translations for the source string identified with that ID.
tr.module does introduce a string translation table, storing menu item translations, etc in that table. It can detect and remove unused translations and can add strings in any language with possible pairings in any other site supported language. That design would fit in here.
We need to think about implementation possibilities. If anyone can add new menu items in any language, we somehow need to store the language used to add that menu item, since then the menu table would be filled with different langugage items. When displaying the menu in a language, we would need to know if we already have the translation itself in the menu item. This starts to get messy with taxonomy terms, profile field names and options and similar things.
So what we can say is that (instead of English), one needs to provide menu items, taxonomy terms, etc at least in the site default language (which could be anything, and is user defined). We would store these strings in the menu, taxonomy, etc tables as primary values, and look up translations for these. This solves the last t() problem. Since the others have suggested solutions, maybe t() will not be that evil for user defined string translation.
What should we do? What is your opinion? Have a better idea?