* _ _ _ _ _ _ _ _ * (_)_ __ | |_ ___ _ __ _ __ __ _| |_(_) ___ _ __ __ _| (_)______ _| |_(_) ___ _ __ * | | '_ \| __/ _ \ '__| '_ \ / _` | __| |/ _ \| '_ \ / _` | | |_ / _` | __| |/ _ \| '_ \ * | | | | | || __/ | | | | | (_| | |_| | (_) | | | | (_| | | |/ / (_| | |_| | (_) | | | | * |_|_| |_|\__\___|_| |_| |_|\__,_|\__|_|\___/|_| |_|\__,_|_|_/___\__,_|\__|_|\___/|_| |_| * "!

The ZCL_I18N class is a general-use class for "coded internationalization" which "! was first introduced for the Sensor board. Its purpose: cater for translations of texts. "! With ZCL_I18N (I18N is short for Internationalization) required translation objects "! can be written in coding. This makes coding readable, portable and it hosts translation of "! single fields, singular and plural form (apple, apples) and multi-line texts.

"! "!

The class can be used with a POOL, which is an identifier for a text pool. With this pool "! comes table ZTEXTPOOL_I18N, which can overwrite the texts provided through coding. Thus: "! the actual translations can be changed and new languages can be added through the ZTEXTPOOL_I18N "! table. Please only update the ZTEXTPOOL_I18N texts through report ZI18N_EDITOR.

"! "!

To use it:

"! CLASS zcl_i18n DEFINITION PUBLIC FINAL CREATE PRIVATE. *** TABLE DEFINITION FOR TABLE ztextpool_i18n ****@EndUserText.label : 'Internationalization text pool - class ZCL_I18N' ****@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE ****@AbapCatalog.tableCategory : #TRANSPARENT ****@AbapCatalog.deliveryClass : #A ****@AbapCatalog.dataMaintenance : #RESTRICTED ****define table ztextpool_i18n { **** **** key client : abap.clnt not null; **** key pool : abap.char(20) not null; **** key id : abap.char(30) not null; **** key lang : abap.char(2) not null; **** key line : abap.int1 not null; **** text : abap.char(100); **** plural : boolean; **** ****} PUBLIC SECTION. TYPES: gty_textline type c length 100. TYPES: gty_textlines TYPE HASHED TABLE OF gty_textline WITH UNIQUE KEY table_line. TYPES: gty_textlinearray TYPE STANDARD TABLE OF gty_textline WITH NON-UNIQUE KEY table_line. TYPES: BEGIN OF gty_textnode, "! The key to a translatable text is it's ID. Upper cased, no special characters, use : as separator id TYPE c LENGTH 30, "! The focused language (used by method get_multiple) text TYPE gty_textlines, "! The supported languages nl TYPE gty_textlines, en TYPE gty_textlines, de TYPE gty_textlines, it TYPE gty_textlines, fr TYPE gty_textlines, es TYPE gty_textlines, "! Purely informational, when singular and plural forms apply to your translation, use the first "! line as singular (Apple) and the second text line as the plural form (Apples). Or use get_plural. plural TYPE boolean, "Single and plural representations END OF gty_textnode. TYPES: gty_textnodes TYPE SORTED TABLE OF gty_textnode WITH UNIQUE KEY id. TYPES: BEGIN OF gty_textnode_oneline, "! The key to a translatable text is it's ID. Upper cased, no special characters, use : as separator id TYPE c LENGTH 30, "! The supported languages nl TYPE gty_textline, en TYPE gty_textline, de TYPE gty_textline, it TYPE gty_textline, fr TYPE gty_textline, es TYPE gty_textline, END OF gty_textnode_oneline. TYPES: gty_textnodes_oneline TYPE SORTED TABLE OF gty_textnode_oneline WITH UNIQUE KEY id. TYPES: BEGIN OF gty_alias, source TYPE c LENGTH 30, target TYPE c LENGTH 30, END OF gty_alias. TYPES: BEGIN OF gty_instance, pool TYPE ztextpool_i18n-pool, i18n TYPE REF TO zcl_i18n, END OF gty_instance. "! Use this class as a singleton-series. Translations can be added (or changed) in numerous rounds. CLASS-DATA gt_instances TYPE SORTED TABLE OF gty_instance WITH UNIQUE KEY pool READ-ONLY. "! DEVELOPMENT ONLY - set this parameter to make the system save all texts it is using. It will populate "! the ZTEXTPOOL_I18N table for used POOLs. It will also overwrite the data on this table. CLASS-DATA gv_supersaver TYPE boolean. "! The class holds a buffer of texts in this attribute. All supported languages included DATA gv_pool TYPE ztextpool_i18n-pool. DATA gt_texts TYPE gty_textnodes READ-ONLY. DATA gv_language TYPE ddlanguage READ-ONLY. DATA gv_secondary_language TYPE ddlanguage READ-ONLY. "! The ALIAS table can be used to make the ID search a bit more flexible. When a text ID is unavailable, "! an alias translation will be attempted where a new ID. Alias values are partial values and should "! start and end with :, e.g. SENSOR:SM37_2:UOM can refer to SENSOR:SM37:UOM with an alias of SM37 (source) "! to SM37_2 (target). DATA gt_alias TYPE SORTED TABLE OF gty_alias WITH UNIQUE KEY source target READ-ONLY. "! The ZCL_I18N class is a singleton pattern class, which will need to be instantiated before use. Instantiation "! can also be done for a POOL, which will return an instance for the pool. This also involves the ZTEXTPOOL_I18N "! table (and it's translations). "!

Fill in a pool name if you intend to translate your texts to other languages.

CLASS-METHODS get_instance IMPORTING iv_pool TYPE char20 DEFAULT '' RETURNING VALUE(ro_instance) TYPE REF TO zcl_i18n. METHODS constructor. "! Set the language if you want to deviate from the logon language. The secondary language can also be set "! here. This will be used when a given translation is unavailable in the in initial language. METHODS set_language IMPORTING iv_language TYPE ddlanguage iv_secondary TYPE ddlanguage DEFAULT 'E'. "! The ALIAS is a sensor-board specific addition to the Internationalization class - it mimics text settings from another "! method through the apply_aliasses method. Not intended for re-use METHODS set_alias IMPORTING iv_source_id TYPE char30 iv_target_id TYPE char30. "! Not intended for re-use METHODS apply_aliasses. "! The multifeed method is where the class will get its translation content from. A table of text nodes "! is passed to this method. Call multifeed as often as you like (translations that are applicable to a "! method can be thus set up on the same method). An example: "!

"!
zcl_i18n=>get_instance( )->multifeed( value #( "!
  ( id = 'GROUPNAME' "!
    nl = value #( ( |Groepsnaam| ) ) "!
    en = value #( ( |Group name| ) ) ) "!
  ( id = 'TITLE' "!
    nl = value #( ( |Titel| ) ) "!
    en = value #( ( |Title| ) ) ) "!
  ( id = 'APPLE' "!
    nl = value #( ( |Appel| ) ( |Appels| ) ) "!
    en = value #( ( |Apple| ) ( |Apples| ) ) plural = abap_true ) "!
) ). "!

METHODS multifeed IMPORTING iv_reset TYPE boolean DEFAULT abap_false it_texts TYPE gty_textnodes. "! feed is the common way to feed texts into the I18N setup. It caters for the most common form only: "! single-line descriptions. If your text is captured in multiple lines, use multifeed instead. "! An example: "!

"!
zcl_i18n=>get_instance( 'default.pool.name' )->feed( value #( "!
  ( id = 'GROUPNAME' nl = 'Groepsnaam' en = 'Group nam' ) "!
  ( id = 'TITLE' nl = 'Titel' en = 'Title' ) "!
) ). "!

METHODS feed IMPORTING iv_reset TYPE boolean DEFAULT abap_false it_texts TYPE gty_textnodes_oneline. "! Save the texts from memory to the ZTEXTPOOL_I18N table, if a pool is defined METHODS save RETURNING VALUE(rv_saved) TYPE boolean. "! Delete text IDor complete pool CLASS-METHODS delete IMPORTING iv_pool TYPE ztextpool_i18n-pool iv_id TYPE ztextpool_i18n-id DEFAULT '' RETURNING VALUE(rv_deleted) TYPE boolean. "! Fetch a singular translation text. This will be the most common method to be used. METHODS get IMPORTING iv_id TYPE any iv_par TYPE any DEFAULT '' iv_par2 TYPE any DEFAULT '' iv_par3 TYPE any DEFAULT '' RETURNING VALUE(rv_text) TYPE string. "! Returns the plural form of the translation. Pass iv_number to let the method "! decide on apple or apples. METHODS get_plural IMPORTING iv_id TYPE any iv_number TYPE any DEFAULT 0 RETURNING VALUE(rv_text) TYPE string. "! Used for the translation objects that have multiple lines. METHODS get_textlines IMPORTING iv_id TYPE any RETURNING VALUE(rt_textlines) TYPE gty_textlines. "! Used for the translation objects that have multiple lines. METHODS get_textlinearray IMPORTING iv_id TYPE any RETURNING VALUE(rt_textlines) TYPE gty_textlinearray. "! Used for group selections, use wildcard characters * or + on "! the iv_id to select multiple textnodes. The content of the textnodes result will have it's translated "! result in attribute rt_textnodes-text[] (all the translation tables will be empty). METHODS get_multiple IMPORTING iv_id TYPE any RETURNING VALUE(rt_textnodes) TYPE gty_textnodes. PRIVATE SECTION. METHODS get_textlines_language IMPORTING iv_textnode TYPE gty_textnode RETURNING VALUE(rt_textlines) TYPE gty_textlines. ENDCLASS. CLASS zcl_i18n IMPLEMENTATION. METHOD get_instance. DATA ls_instance TYPE gty_instance. FIELD-SYMBOLS: TYPE gty_textlines. READ TABLE gt_instances INTO ls_instance WITH KEY pool = iv_pool. IF sy-subrc <> 0. ls_instance-pool = iv_pool. CREATE OBJECT ls_instance-i18n. INSERT ls_instance INTO TABLE gt_instances. ENDIF. ls_instance-i18n->gv_pool = iv_pool. IF iv_pool <> ''. "When instantiation is done with a reference to a POOL. The texts from the POOL are involved SELECT id, lang, line, text, plural FROM ztextpool_i18n INTO TABLE @DATA(lt_textpool) WHERE pool = @iv_pool. "#EC CI_SEL_NESTED IF sy-subrc = 0. "Texts available in the text pool table - apply LOOP AT lt_textpool INTO DATA(ls_textpool). READ TABLE ls_instance-i18n->gt_texts ASSIGNING FIELD-SYMBOL() WITH KEY id = ls_textpool-id. IF sy-subrc <> 0. INSERT VALUE #( id = ls_textpool-id plural = ls_textpool-plural ) INTO TABLE ls_instance-i18n->gt_texts ASSIGNING . ENDIF. DATA(lv_fieldname) = |-{ ls_textpool-lang CASE = UPPER }|. ASSIGN (lv_fieldname) TO . IF sy-subrc = 0. TRY. INSERT ls_textpool-text INTO TABLE . CATCH cx_sy_itab_duplicate_key. "We catch, and simply ignore ENDTRY. ENDIF. ENDLOOP. ENDIF. ENDIF. ro_instance = ls_instance-i18n. ENDMETHOD. METHOD constructor. set_language( sy-langu ). ENDMETHOD. METHOD set_alias. INSERT VALUE gty_alias( source = iv_source_id target = iv_target_id ) INTO TABLE gt_alias. ENDMETHOD. METHOD set_language. gv_language = iv_language. gv_secondary_language = iv_secondary. ENDMETHOD. METHOD multifeed. DATA ls_textnode TYPE gty_textnode. FIELD-SYMBOLS TYPE gty_textnode. IF iv_reset = abap_true. " Reset the texts table (ignore previous feeds and textfeed entries from ZTEXTPOOL_I18N CLEAR gt_texts. ENDIF. LOOP AT it_texts INTO ls_textnode. READ TABLE gt_texts ASSIGNING WITH KEY id = ls_textnode-id. IF sy-subrc = 0. "The text-node is already available. Only empty translations will be added - anything else is ignored: reason: the "entries may already be available from the text pool - and the pool is leading. IF lines( -nl ) = 0. -nl[] = ls_textnode-nl[]. ENDIF. IF lines( -en ) = 0. -en[] = ls_textnode-en[]. ENDIF. IF lines( -de ) = 0. -de[] = ls_textnode-de[]. ENDIF. IF lines( -it ) = 0. -it[] = ls_textnode-it[]. ENDIF. IF lines( -fr ) = 0. -fr[] = ls_textnode-fr[]. ENDIF. IF lines( -es ) = 0. -es[] = ls_textnode-es[]. ENDIF. ELSE. INSERT ls_textnode INTO TABLE gt_texts. ENDIF. ENDLOOP. "This feature will populate the ZTEXTPOOL_I18N table with the texts that were specified in coding. Purpose: "can make is easier to do translations. BEWARE: existing texts are overwritten. When unsure just make a "JSON download in report ZI18N_EDITOR IF gv_supersaver = abap_true. save( ). ENDIF. ENDMETHOD. METHOD feed. DATA lt_textnodes TYPE gty_textnodes. LOOP AT it_texts INTO DATA(ls_textnode_oneline). INSERT VALUE gty_textnode( id = ls_textnode_oneline-id ) INTO TABLE lt_textnodes ASSIGNING FIELD-SYMBOL(). IF ls_textnode_oneline-nl <> ''. INSERT ls_textnode_oneline-nl INTO TABLE -nl. ENDIF. IF ls_textnode_oneline-en <> ''. INSERT ls_textnode_oneline-en INTO TABLE -en. ENDIF. IF ls_textnode_oneline-de <> ''. INSERT ls_textnode_oneline-de INTO TABLE -de. ENDIF. IF ls_textnode_oneline-fr <> ''. INSERT ls_textnode_oneline-fr INTO TABLE -fr. ENDIF. IF ls_textnode_oneline-it <> ''. INSERT ls_textnode_oneline-it INTO TABLE -it. ENDIF. IF ls_textnode_oneline-es <> ''. INSERT ls_textnode_oneline-es INTO TABLE -es. ENDIF. ENDLOOP. multifeed( it_texts = lt_textnodes iv_reset = iv_reset ). ENDMETHOD. METHOD save. DATA lt_ztextpool_i18n TYPE STANDARD TABLE OF ztextpool_i18n. DATA ls_textid TYPE gty_textnode. DATA lv_text TYPE ztextpool_i18n-text. DATA lv_int1 TYPE int1. DEFINE populate. CLEAR lv_int1. LOOP AT ls_textid-&1 INTO lv_text. APPEND VALUE #( pool = gv_pool id = ls_textid-id lang = |{ '&1' CASE = UPPER }| line = lv_int1 text = lv_text plural = ls_textid-plural ) TO lt_ztextpool_i18n. lv_int1 = lv_int1 + 1. ENDLOOP. END-OF-DEFINITION. rv_saved = abap_false. CHECK gv_pool <> ''. DATA(ls_rstable) = VALUE rstable( tabname = 'ZTEXTPOOL_I18N' varkey = gv_pool ). CALL FUNCTION 'ENQUEUE_E_TABLE' EXPORTING tabname = ls_rstable-tabname varkey = ls_rstable-varkey EXCEPTIONS OTHERS = 4. CHECK sy-subrc = 0. LOOP AT gt_texts INTO ls_textid. populate: nl, en, de, fr, it, es. ENDLOOP. "Apply the changes - where the current pool entries are cleared first: DELETE FROM ztextpool_i18n WHERE pool = gv_pool. "#EC CI_IMUD_NESTED "Insert the new entries INSERT ztextpool_i18n FROM TABLE lt_ztextpool_i18n. "#EC CI_IMUD_NESTED CALL FUNCTION 'DEQUEUE_E_TABLE' EXPORTING tabname = ls_rstable-tabname varkey = ls_rstable-varkey. rv_saved = abap_true. ENDMETHOD. METHOD delete. rv_deleted = abap_false. CHECK iv_pool <> ''. DATA(ls_rstable) = VALUE rstable( tabname = 'ZTEXTPOOL_I18N' varkey = iv_pool ). CALL FUNCTION 'ENQUEUE_E_TABLE' EXPORTING tabname = ls_rstable-tabname varkey = ls_rstable-varkey EXCEPTIONS OTHERS = 4. CHECK sy-subrc = 0. if iv_id = ''. DELETE FROM ztextpool_i18n WHERE pool = iv_pool. else. DELETE FROM ztextpool_i18n WHERE pool = iv_pool and id = iv_id. endif. CALL FUNCTION 'DEQUEUE_E_TABLE' EXPORTING tabname = ls_rstable-tabname varkey = ls_rstable-varkey. rv_deleted = abap_true. ENDMETHOD. METHOD apply_aliasses. LOOP AT gt_alias INTO DATA(ls_alias). LOOP AT gt_texts INTO DATA(rs_textnode) WHERE id CS |:{ ls_alias-target }:|. "#EC CI_SORTSEQ REPLACE |:{ ls_alias-target }:| IN rs_textnode-id WITH |:{ ls_alias-source }:|. INSERT rs_textnode INTO TABLE gt_texts. ENDLOOP. ENDLOOP. ENDMETHOD. METHOD get. READ TABLE gt_texts INTO DATA(ls_textnode) WITH KEY id = iv_id. IF sy-subrc = 0. DATA(lt_textlines) = get_textlines_language( ls_textnode ). LOOP AT lt_textlines INTO rv_text. EXIT. ENDLOOP. ELSE. rv_text = iv_id. ENDIF. IF iv_par IS SUPPLIED. REPLACE '&' WITH iv_par INTO rv_text. ENDIF. IF iv_par2 IS SUPPLIED. REPLACE '&' WITH iv_par2 INTO rv_text. ENDIF. IF iv_par3 IS SUPPLIED. REPLACE '&' WITH iv_par3 INTO rv_text. ENDIF. CONDENSE rv_text. ENDMETHOD. METHOD get_plural. READ TABLE gt_texts INTO DATA(ls_textnode) WITH KEY id = iv_id. IF sy-subrc = 0. DATA(lt_textlines) = get_textlines_language( ls_textnode ). IF iv_number = 1. LOOP AT lt_textlines INTO rv_text. EXIT. ENDLOOP. ELSE. LOOP AT lt_textlines INTO rv_text. ENDLOOP. ENDIF. ELSE. rv_text = iv_id. ENDIF. ENDMETHOD. METHOD get_textlines. READ TABLE gt_texts INTO DATA(ls_textnode) WITH KEY id = iv_id. IF sy-subrc = 0. rt_textlines = get_textlines_language( ls_textnode ). ENDIF. ENDMETHOD. METHOD get_textlinearray. READ TABLE gt_texts INTO DATA(ls_textnode) WITH KEY id = iv_id. LOOP AT get_textlines_language( ls_textnode ) INTO DATA(lv_line). APPEND lv_line TO rt_textlines. ENDLOOP. ENDMETHOD. METHOD get_multiple. DATA ls_textnode_new TYPE gty_textnode. LOOP AT gt_texts INTO DATA(ls_textnode) WHERE id CP iv_id. "#EC CI_SORTSEQ CLEAR ls_textnode_new. ls_textnode_new-id = ls_textnode-id. ls_textnode_new-text = get_textlines_language( ls_textnode ). INSERT ls_textnode_new INTO TABLE rt_textnodes. ENDLOOP. ENDMETHOD. METHOD get_textlines_language. CASE gv_language. WHEN 'N'. rt_textlines[] = iv_textnode-nl[]. WHEN 'E'. rt_textlines[] = iv_textnode-en[]. WHEN 'D'. rt_textlines[] = iv_textnode-de[]. WHEN 'I'. rt_textlines[] = iv_textnode-it[]. WHEN 'F'. rt_textlines[] = iv_textnode-fr[]. ENDCASE. IF lines( rt_textlines ) = 0. CASE gv_secondary_language. WHEN 'N'. rt_textlines[] = iv_textnode-nl[]. WHEN 'E'. rt_textlines[] = iv_textnode-en[]. WHEN 'D'. rt_textlines[] = iv_textnode-de[]. WHEN 'I'. rt_textlines[] = iv_textnode-it[]. WHEN 'F'. rt_textlines[] = iv_textnode-fr[]. ENDCASE. ENDIF. ENDMETHOD. ENDCLASS.