Showing posts with label multilingual. Show all posts
Showing posts with label multilingual. Show all posts

11 August 2025

Using PL/SQL to Seed and Publish Oracle APEX application translations

For multilingual applications, it can become a little tiresome to constantly have to reseed and republish your application while testing multiple languages or debugging language-switch problems.
The other day, while tracking down a tricky language-switch bug, I thought to myself that there must be a faster way to seed and publish.  So I pulled up the documentation for the APEX_LANG API and found these two procedures: apex_lang.seed_translations and apex_lang.publish_application.
Using these two procedures meant that I could write a little script to seed and publish my application (ID 12345) in French and Spanish:
begin
  apex_lang.seed_translations   
     (p_application_id => 12345, p_language => 'fr');
  apex_lang.publish_application 
     (p_application_id => 12345, p_language => 'fr');

  apex_lang.seed_translations
     (p_application_id => 12345, p_language => 'es');
  apex_lang.publish_application 
     (p_application_id => 12345, p_language => 'es');
end;
Note, however, that when running the above outside an APEX environment, this script fails to execute. This because the security group for this workspace needs to be manually set. The error message looks like this:
ORA-20001: Package variable g_security_group_id must be set.
ORA-06512: at "APEX_240200.WWV_FLOW_IMP", line 109
ORA-06512: at "APEX_240200.HTMLDB_LANG", line 328
To set the security group for the workspace, we first have to retrieve the workspace ID and then call the apex_util.set_security_group_id procedure. An additional complication is that a schema may be associated with more than one APEX workspace, but we'll deal with that shortly.
declare
  l_workspace_id  apex_workspaces.workspace_id%type;
begin
  -- get the workspace ID for this schema 
  -- (for now, let's assume there's only 1) 
  select workspace_id 
    into l_workspace_id
    from apex_workspaces;  
  --
  -- set security group for the Oracle APEX workspace 
  apex_util.set_security_group_id (l_workspace_id);
  
  -- seed and publish french
  apex_lang.seed_translations   
     (p_application_id => 12345, p_language => 'fr');
  apex_lang.publish_application 
     (p_application_id => 12345, p_language => 'fr');

  -- seed and publish spanish
  apex_lang.seed_translations
     (p_application_id => 12345, p_language => 'es');
  apex_lang.publish_application 
     (p_application_id => 12345, p_language => 'es');
end;
Well, that takes care of my immediate issue for application 12345 and the French and Spanish translations.  
But.... Wouldn't it be so much better if we could have a solution that would check whether any of my applications need seeding and publishing, and that would then seed and publish them as needed?
We can start building one by querying the APEX data dictionary view apex_application_trans_map.  The requires_synchronization column tells us which language translations need to be seeded and published. This query returns results for all of the potential multiple APEX workspaces associated with the current schema. So it handles the multiple workspace issue alluded to earlier in this blog post.
select t.*
  from apex_application_trans_map t
 where substr(t.requires_synchronization,1,1) = 'Y'
 order by t.primary_application_id,
          t.translated_app_language;
We've got pretty much everything that we need to put it all of this together as a procedure:

1. Let's loop through an SQL query that retrieves every language translation (translated_app_language) that needs to be synchronized for this schema along with its workspace, application name and ID.

2. Within the loop, whenever there's a change of workspace, we'll reset the security group.

3. We'll then seed and publish the application translation.
4. The procedure will accept a single parameter, p_app_id.  If this parameter is passed with a value of null, it will loop through, seed and publish all APEX applications that need to be synchronized.  This will be done across all the APEX workspaces associated with the current schema.
procedure seedAndPublishTranslationsApp (
  p_app_id in apex_application_trans_map.primary_application_id%type
) is
  l_workspace_id apex_workspaces.workspace_id%type := 0;
begin
  --
  -- Loop through all of this schema's workspaces that have applications
  -- with translation languages that require synchronisation.
  --
  for rec_trans in (
    select t.workspace,
           t.primary_application_name,
           t.primary_application_id,
           t.translated_app_language,
           w.workspace_id
      from apex_application_trans_map t
           join apex_workspaces w
             on w.workspace = t.workspace  -- to get workspace ID
     where t.primary_application_id = nvl(p_app_id, t.primary_application_id) -- null means all apps
       and substr(t.requires_synchronization, 1, 1) = 'Y'
     order by w.workspace_id,
              t.primary_application_id,
              t.translated_app_language
  ) loop
  
    -- If there's a change of workspace, (re)set the security group.
    if l_workspace_id <> rec_trans.workspace_id then
      dbms_output.put_line('Workspace: "' || rec_trans.workspace || '":');
      apex_util.set_security_group_id(rec_trans.workspace_id);
      l_workspace_id := rec_trans.workspace_id;
    end if;

    apex_lang.seed_translations(
      p_application_id => rec_trans.primary_application_id,
      p_language       => rec_trans.translated_app_language
    );

    apex_lang.publish_application(
      p_application_id => rec_trans.primary_application_id,
      p_language       => rec_trans.translated_app_language
    );

    dbms_output.put_line(
      'Seeded and published language: "' || rec_trans.translated_app_language ||
      '" for application: ' || rec_trans.primary_application_id || ' - ' ||
      rec_trans.primary_application_name
    );
  end loop;

end seedAndPublishTranslationsApp;
As I don't particularly like having free-floating procedures and functions cluttering up my database, I've made a package, translateTools, that encapsulates this procedure.  You can download the package here.
There are various ways that you can use this procedure.
For example, during development, I like to have a worksheet open that let's me quickly run
  exec translateTools.seedAndPublishTranslationsApp();
whenever I want to. It certainly saves me a lot of clicking within the APEX application development environment.

Another use-case could be to implement it as a regular job that will run nightly to ensure that there are no unpublished translations left lying around.

It's a small solution to a small problem. I hope that some of you will find it useful.

Happy APEXing and Happy Translating!!

19 August 2022

Correctly sorting data containing accented characters (a.k.a. the Côte d'Ivoire and Türkiye issue)

This will be short and sweet. Hopefully it will be useful to some of you.

As you may or may not know, the country formerly known as Turkey recently changed its official name to Türkiye , even in English. However, when sorting by country name, it should appear between Tunisia and Turkmenistan rather than later in the list. (Official U.N. sorted list can be found here.)

In the weeks following this change, I was asked on at least five separate occasions to look at sorting issues that arose. Please accept my apologies in advance if you already know what I'm about to write. I thought it was common knowledge, but recent experience has shown otherwise.

Below, I'll explain how I correctly sort by country within Oracle. This technique also applies to any other data (names, etc.) that need to take accented characters into account when sorting. I hope it's useful to some of you.

Note: What comes below applies principally to Latin alphabets. I haven’t tested on non-Latin alphabets, but I suspect a similar approach exists.

First, demonstrating the incorrect sort

This is what some use by default — the problem is that accented characters are sorted after non-accented letters.

So we can see that both Côte d'Ivoire and Türkiye are incorrectly sorted here and are placed after their peers.

SQL> select country_name from vw_temp_countries order by country_name;

COUNTRY_NAME
-------------
Cook Islands
Costa Rica
Croatia
Cuba
Curaçao
Cyprus
Czechia
Côte d'Ivoire
Tunisia
Turkmenistan
Tuvalu
Türkiye

13 rows selected.

Now, a correct sort

With this sort, accented characters are taken into account and sorted appropriately. The Oracle NLSSORT function ensures the correct order. The NLS_SORT parameter is set to swiss, as this setting accommodates most Latin-based languages.

SQL> select country_name 
         from vw_temp_countries
         order by nlssort(country_name, 'NLS_SORT = swiss');

COUNTRY_NAME
-------------
Cook Islands
Costa Rica
Côte d'Ivoire
Croatia
Cuba
Curaçao
Cyprus
Czechia
Tunisia
Türkiye
Turkmenistan
Tuvalu

13 rows selected.

That's it — as promised, short and sweet! 😀

15 September 2020

Making SQL Developer's UI use your preferred language

Recently I was using Oracle's SQL Developer at a client site where all the virtual PCs were configured for a non-English language and my SQL Developer was choosing this language by default for the User Interface.

I'm so used to working with SQL Developer in English that I found it a little distracting to read the menu options, etc. in a different language - even if it's a language that I use on a daily basis.

After a short time searching online I came across this quick fix written by Matthias Karl Schulz in 2011 - and it is essentially still valid today in SQL Developer 20.2.  

To summarise, here's what you need to do:

  1. Find the sqldeveloper.conf file inside your SQL Developer installation. 

    On a PC I found this at
    ...sqldeveloper\sqldeveloper\bin\sqldeveloper.conf

    On MacOS it's at /Applications/SQLDeveloper.app/Contents/Resources/sqldeveloper/sqldeveloper/bin/sqldeveloper.conf
    (Note: On MacOS you should right click on the SQLDeveloper.app file and choose "Show Package Contents" to open these directories.)

  2. For English, just add the following line to the file.  For other languages, you can replace the "=en" by the ISO 639-1 two-letter code for whichever language you wish to force SQL Developer to use.  I've tried German (de) and Spanish (es) just to see how it looks and it works just fine.

    AddVMOption -Duser.language=en

  3. Save the file

  4. Close SQL Developer if it is already open

  5. Restart SQL Developer. 
And there you have it, SQL Developer will now open in the language of your choice.  
Not all languages are available.  For example, I tried the Irish language ("ga" in ISO 639-1) but, as I expected, it wasn't available (yet).