-5

For context, I am trying to do some simplistic localization, as a first step into that "ocean", with limited language options. I want to be more "inclusive" than just limited to itemizing all instances of the type (three options for values of Bash environment variable LANGUAGE).

#define SPANISH 'es_MX:es'
#define FRENCH  'fr_CA:fr'
#define ENGLISH 'en_CA:en'

In AWK, I can do

MYLANG = sprintf("%2s", LANG)

I want to do something that simple, so I can do

// AWK syntax for sprintf
#define   MYLANG   {simple}

to obtain only the first two characters of the string LANG.

Is there something that I can use for that?

The full working script, all behaving as expected except for not having access to the environment variable LANGUAGE is the following:

#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

#define SPANISH 'es'
#define FRENCH  'fr'
#define ENGLISH 'en'


#ifndef LANGUAGE
#include "CIMIfrecN_MessagesTable_fr.h"
#else

#define MYLANG LANGUAGE[0],LANGUAGE[1]        // Not valid

#if MYLANG == SPANISH
//#include "_cimifrecn_messagestable_es_h"
#include "CIMIfrecN_MessagesTable_es.h"
#elif MYLANG == FRENCH
//#include "_cimifrecn_messagestable_fr_h"
#include "CIMIfrecN_MessagesTable_fr.h"
#else
//#include "_cimifrecn_messagestable_en_h"
#include "CIMIfrecN_MessagesTable_en.h"
#endif
#endif
//==================================================================================================
//    START OF MAIN PROGRAM
//==================================================================================================

int main() {

    char yellowON[12] = "\e[93;1m";
    char yellowOFF[12] = "\e[0m";

    char redON[12] = "\e[91;1m";
    char redOFF[12] = "\e[0m";

    char blueON[12] = "\e[94;1m";
    char blueOFF[12] = "\e[0m";

    char ThisLOCALE[30];

    sprintf(ThisLOCALE, "%s", setlocale(LC_ALL, ""));
    printf("\n\t %s\n\n", ThisLOCALE);


    printf("\n    == %s%s%s ==\n", blueON, ScenarioSetUp, blueOFF);

    return 0;
}

The outputs are displayed as follows:

enter image description here

That shows that the proposed method using two single-position references to LANGUAGE does not work. :-(

<Addendum 1>

So, I tried a revised version of that script, where I do compare to the full value of the LANGUAGE variable, thinking that it demonstrated that it was accessible during the pre-processing. Unfortunately, I did not realize I had English as the default fallback case, so my conclusion was incorrect. I had received a false-positive. :-(

...(deleted)...

<Addendum 2>

I revised my program again, setting English for a specific value match, and added specific strings to identify which of the invalid conditions are encountered:

#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

#define SPANISH 'es_MX:es'        // Format reported by bash environment variable LANGUAGE
#define FRENCH  'fr_CA:fr'
#define ENGLISH 'en_CA:en'
//#define SPANISH 'es'
//#define FRENCH  'fr'
//#define ENGLISH 'en'

//#define TESTOR LANGUAGE[0],LANGUAGE[1]    // Not valid
#define TESTOR LANGUAGE

//#ifndef LANG
#ifndef TESTOR
    char ScenarioSetUp[30] = "TESTOR was not defined";
//#include "_cimifrecn_messagestable_es_h"
#include "CIMIfrecN_MessagesTable_es.h"
#else

#if TESTOR == NULL
    char ScenarioSetUp[50] = "LANGUAGE was not obtained from the environment";
#elif TESTOR == SPANISH

//#include "_cimifrecn_messagestable_es_h"
#include "CIMIfrecN_MessagesTable_es.h"
#elif TESTOR == ENGLISH
//#include "_cimifrecn_messagestable_en_h"
#include "CIMIfrecN_MessagesTable_en.h"
#else
//#include "_cimifrecn_messagestable_fr_h"
#include "CIMIfrecN_MessagesTable_fr.h"
#endif

#endif

//==================================================================================================
//    START OF MAIN PROGRAM
//==================================================================================================

//int setProgramLOCALE() {
int main() {

    char yellowON[12] = "\e[93;1m";
    char yellowOFF[12] = "\e[0m";

    char redON[12] = "\e[91;1m";
    char redOFF[12] = "\e[0m";

    char blueON[12] = "\e[94;1m";
    char blueOFF[12] = "\e[0m";

    char ThisLOCALE[30];

    sprintf(ThisLOCALE, "%s", setlocale(LC_ALL, ""));
    printf("\n\t %s\n\n", ThisLOCALE);


    printf("\n    == %s%s%s ==\n", blueON, ScenarioSetUp, blueOFF);

    return 0;
}

This time, it definitely shows specifically failure to detect LANGUAGE for the assignment to TESTOR for compiler directives, as can be seen by the message reported:

Enter image description here

So I am still looking for a way to get at LANGUAGE during compiler directives. :-(

Please help.

Sample include file:

//==================================================================================================
//    FILE:    CIMIfrecN_MessagesTable_fr.h
//    Header initializing context-based messages for the program
//==================================================================================================

#ifndef _cimifrecn_messagestable_fr_h
#define _cimifrecn_messagestable_fr_h

//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>

    char ScenarioSetUp[91]                = "configuration du scénario";
    char EnterDesiredFrequency[91]        = "Veuillez entrer la fréquence désirée";
    char DesiredFrequency[91]             = "Fréquence désirée";
    char NumberLevelsImplied[91]          = "Nombre de niveaux impliqués";
    char NumberStrutsRequired[91]         = "Nombre de poutres nécessaires";
    char EvaluationPointRelevance[91]     = "Évaluation des point du grillage pour identifier leur pertinence";
    char Considering[91]                  = "Examinons";
    char Horizontals[91]                  = "Horizontales";
    char NoneMetCriteria[91]              = "(aucunes comblait les critères)";
    char Diagnonals[91]                   = "Diagonales";
    char VerticesMinimalSet[91]           = "Intersections identifiées pour un ensemble minimal contraignant entièrement la grille";
    char ThereAre[91]                     = "Il y a";
    char Vertices[91]                     = "Intersections";
    char VerteIndexMatrix[91]             = "Matrice des indices pour Intersections";
    char VerticesProjectedSphere[91]      = "Liste des intersections du polyhèdre projetés sur la sphère";
    char EvalStrutelevance[91]            = "Évaluation des poutres pour déterminer leur pertinence";
    char HorizontalStruts[91]             = "Poutres Horizontales";
    char DiagonalStruts[91]               = "Poutres Diagonales";
    char PreOptimizationStruts[91]        = "Groupe maximal de poutres requis à être optimisés pour l'ensemble minimum";
    char StrutsAndTheirLengthsAre[91]     = "poutres et leur longeurs sont";

#endif /* _cimifrecn_messagestable_fr_h */
//==================================================================================================

Script to generate language specific header file from comprehensive master message file:

#!/bin/sh

lang="${1}"

MESSAGE_TABLE="CIMIfrecN_MessagesTable_ALL.h"
MESSAGE_HEADER="CIMIfrecN_MessagesTable_${lang}.h"
Module="_"`echo "${MESSAGE_HEADER}" | tr '[A-Z]' '[a-z]' `
Module=`basename "${Module}" ".h" `"_h"

maxVar=`grep '^V|' "${MESSAGE_TABLE}" | cut -f2- -d\| |
    while read msg
    do
        echo "${msg}" | wc -c
    done | sort -r | head -1 `

maxStr=`grep '^'${lang}'|' "${MESSAGE_TABLE}" | cut -f2- -d\| |
    while read msg
    do
        echo "${msg}" | wc -c
    done | sort -r | head -1 `

#echo ${maxStr}

awk -v maxV="${maxVar}" -v maxS="${maxStr}" -v local="${lang}" -v file="${MESSAGE_HEADER}" -v module="${Module}" 'BEGIN{
    var="" ;
    msg=""
    printf("\t local = %s\n", local ) | "cat 1>&2" ;

    printf("//==================================================================================================\n") ;
    printf("//    FILE:    %s\n", file ) ;
    printf("//    Header initializing context-based messages for the program\n") ;
    printf("//==================================================================================================\n") ;
    printf("\n") ;

    printf("#ifndef %s\n", module ) ;    ###    Insert "header guard" to avoid double referencing by compiler
    printf("#define %s\n", module ) ;
    printf("\n") ;

    printf("//#include <stdio.h>\n") ;
    printf("//#include <stdlib.h>\n") ;
    printf("//#include <string.h>\n") ;
    printf("\n") ;
}{
    switch( $0 ){
        case /\|/ :
            #printf("\n %s\n", $1 ) ;
            gsub( "\015", "", $0 );

            split( $0, dat, "|" ) ;

            if( dat[1] == "V" ){
                var=dat[2] ;
                #printf("\n\t %s\n", var ) ;
            }else{
                #printf("\n %s\n", $1 ) ;
                if( dat[1] ~ local ){
                    msg=dat[2] ;
                    #printf("\n\t %s\n", msg ) ;
                } ;
            } ;

            if( var != "" && msg != "" ){
                #printf("\n\t\t var = %s\n", var ) ;
                #printf("\t\t msg = %s\n", msg ) ;
                offset=maxV-length(var)+3 ;
                printf("\tchar %s\133%d\135%"offset"s= %s ;\n", var, maxS, "", msg ) ;
                var=""
                msg=""
            } ;
            break ;
        default :
            break ;
    } ;
}END{
    printf("\n#endif /* %s */\n", module ) ;
    printf("//==================================================================================================\n") ;
}' "${MESSAGE_TABLE}" > "${MESSAGE_HEADER}"

gvim "${MESSAGE_HEADER}"

echo "\n\t Done.\n"
#//3456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+
22
  • 3
    Re “Is there something that I can use for that?”: Yes, write an awk script that does the processing you want and run it on your source code before compiling. You are not limited to using the preprocessing built into C; you can do any preprocessing you want using other programs. Commented Oct 6, 2023 at 12:36
  • 2
    @EricMarceau BTW shouldn't it be "es_MX:es" instead of 'es_MX:es'? Commented Oct 6, 2023 at 12:44
  • 2
    Both awk sprintf("%2s",LANG) and C/C++ sprintf(buf,"%2s",LANG) do NOT "obtain only the first 2 characters" of LANG; they give the whole value (which in the C/C++ case may overflow the buffer). Using "%.2s" -- WITH A PERIOD character before the count -- DOES limit the result to 2 characters. In awk so does substr(LANG,1,2). Commented Oct 6, 2023 at 12:46
  • 4
    I do not understand. How are SPANISH FRENCH macros related to extracting first 2 characters of a string? How is awk related to extarcting first 2 characters of a string? Why not just char MYLANG[2] = {LANG[0], LANG[1]};? that I can use for that? For what exact do you want to use something? If you want to use awk, use awk. No, it is not possible to parse strings in preprocessor, preprocessor doesn't know about strings. Do you want to use C programming language or specifically C preprocessor?
    – KamilCuk
    Commented Oct 6, 2023 at 12:56
  • 4
    Bottom line, what are you trying to do? Are you asking XY question? If you want to create your program translations, i.e. doing localization, consider using locale LC_LANG or LANGAUGE environment variable and using an existing project for that, like the amazing gnu.org/software/gettext . Gettext is very widely used both in C programming and Linux world.
    – KamilCuk
    Commented Oct 6, 2023 at 13:08

2 Answers 2

1

Updated answer...

Try to use this code...

#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

// Define preprocessor macros for each language
#ifdef SPANISH
#define MYLANG "es_MX:es"
#include "CIMIfrecN_MessagesTable_es.h"
#endif

#ifdef FRENCH
#define MYLANG "fr_CA:fr"
#include "CIMIfrecN_MessagesTable_fr.h"
#endif

#ifdef ENGLISH
#define MYLANG "en_CA:en"
#include "CIMIfrecN_MessagesTable_en.h"
#endif

//==================================================================================================
//  START OF MAIN PROGRAM
//==================================================================================================

int main() {
    char yellowON[12] = "\e[93;1m";
    char yellowOFF[12] = "\e[0m";

    char redON[12] = "\e[91;1m";
    char redOFF[12] = "\e[0m";

    char blueON[12] = "\e[94;1m";
    char blueOFF[12] = "\e[0m";

    char ThisLOCALE[30];

    sprintf(ThisLOCALE, "%s", setlocale(LC_ALL, ""));
    printf("\n\t %s\n\n", ThisLOCALE);

    printf("Selected language: %s\n", MYLANG);

    printf("\n  == %s%s%s ==\n", blueON, ScenarioSetUp, blueOFF);

    return 0;
}

Now, you should be able to compile your program with the right language preprocessor macro.

Here are examples of how you’d do this.

Spanish:

gcc -DSPANISH -o my_program my_program.c

French:

gcc -DFRENCH -o my_program my_program.c

English:

gcc -DENGLISH -o my_program my_program.c

The individual compilation should now include the language-specific header file and set MYLANG macro accordingly. Then you should be able to switch between languages at compile.

I don't believe you need to make any changes to your "Script to generate language specific header file from comprehensive master message file"

Your Include file seems to be fine... Just remember that each language-specific file should be structured the same but translated in the respective language so your program can use the correct translation.

Assuming you've made similar changes to each of the other language-specific message files (e.g., CIMIfrecN_MessagesTable_es.h, CIMIfrecN_MessagesTable_en.h), I'd think the structure should work correctly.

4
  • Thank you,@masonthedev. I have already successfully constructed with this approach for the variable identification and evaluation. The problem is that I then want to have the compiler include the header file containing a shared list of variables, but the strings are in the language specific to the name of the file containing the language short-form of es, fr, en, etc. My understanding is that I can't insert an #include within a main/function . Commented Oct 7, 2023 at 1:00
  • @EricMarceau See if that update helps. Commented Oct 7, 2023 at 5:43
  • 1
    Thank you @masonthedev. I will work with that answer, and give you credit for it. However, expanding on those individual cases, I would like to add #define LANGSET '1' for each that is a matched case, then at the bottom of that list, add #ifndef LANGSET to have the compile set my definition of a failsafe fallback definition. But for some reason, it is not correctly recognizing that if i use -DNONE , the ifndef is not being triggered. I don't know why. Commented Oct 7, 2023 at 23:36
  • 1
    I removed the single quotes around the 1 for #define LANGSET 1 , and that now works perfectly. Thank you all for your various hints and guidance. Much appreciated! Commented Oct 7, 2023 at 23:59
1

Final working version of C demonstrator program, using method outlined in manner I understood by @masonthedev, is as follows:

/*##################################################################################################
###
###  $Id: $
###
###  C source code for demonstrating usage of compiler
###  directive for selecting a language specific header
###  file for standardized messages used by a given program.
###
###  The program expects the compiler command to include the
###  passing of a language identification string, i.e.
###  "Español" for the compiler to make the correct
###  selection from an itemized list of scenarios.
###
###  It is recommended that the header filenames make
###  use of the ISO standardized 2-letter labels for
###  the language identifier.
###
###  This demo program also calls the LOCALE identification
###  function directly for a comparison of the value
###  identified and reports both that value and the
###  first message variable's value defined in the
###  header file for visual comparison as a sanity
###  check that all went as expected/desired.
###
####################################################################################################
*/

#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>


#ifdef Español
#include "CIMIfrecN_MessagesTable_es.h"
//#define MYLANG "es_MX:es"
    char MYLANG[15] = "es" ;
#define LANGSET 1
#endif

#ifdef Français
#include "CIMIfrecN_MessagesTable_fr.h"
//#define MYLANG "fr_CA:fr"
    char MYLANG[15] = "fr" ;
#define LANGSET 1
#endif

#ifdef English
#include "CIMIfrecN_MessagesTable_en.h"
//#define MYLANG "en_CA:en"
    char MYLANG[15] = "en" ;
#define LANGSET 1
#endif

#if LANGSET != 1
// Failsafe if language directive not set or not recognized
#include "CIMIfrecN_MessagesTable_en.h"
//#define MYLANG "en_CA:en"
    char MYLANG[15] = "en" ;
#endif



//==================================================================================================
//    START OF MAIN PROGRAM
//==================================================================================================

//int setProgramLOCALE(){
int main(){

    char yellowON[12]="\e[93;1m" ;
    char yellowOFF[12]="\e[0m" ;

    char redON[12]="\e[91;1m" ;
    char redOFF[12]="\e[0m" ;

    char blueON[12]="\e[94;1m" ;
    char blueOFF[12]="\e[0m" ;

    char ThisLOCALE[30] ;


    sprintf( ThisLOCALE, "%s", setlocale(LC_ALL, "") ) ;
    printf("\n\t %s\n\n", ThisLOCALE ) ;

    printf("\t Selected language: %s\n", MYLANG);

    printf("\n\t == %s%s%s ==\n\n", blueON, ScenarioSetUp, blueOFF ) ;

    return 0;
}

That was compiled using

g++ -DEnglish -Wall -w -o CIMIfrecN_GetMessageArray_LOCALE__TESTOR CIMIfrecN_GetMessageArray_LOCALE__TESTOR.cpp

The resulting output was as desired:

en_CA.UTF-8

Selected language: en

== Scenario Set-Up ==

Using -DBOGUS to test the #ifndef LANGSET confirmed that the failsafe option (temporarily set to a value different from the local value) was in fact correctly triggered, confirming the logic.

I created a wrapper script to handle that pre-compile setup:

#!/bin/sh

####################################################################################################
###
###  $Id: Compile_Manually.sh,v 1.1 2023/10/07 22:55:30 ericthered Exp ericthered $
###
###  Script to compile programs using compiler directive
###  for each user's own locale with a view to selecting
###  a language specific header file for standardized
###  messages used by the program.
###
###  The script will signal the compiler which of the
###  locale-specific message header files will be
###  used during compile time.
###
###  This script does not care what the name of the
###  language-specific header files are.
###  Those are expected to be defined within the compiler
###  directives of the Program being compiled.
###
###  The --debug option allows the user to test
###  the LOCALE functionality by:
###
###    - probing the environment and reporting LOCALE
###      specific information, and
###    - using an EXAMPLE program demonstrating how
###      that is incorporated via compiler directives.
###
####################################################################################################

probe_LOCALE(){
    echo "\t CMD: locale"
    locale
    echo "==============================================\n"

    echo "\t CMD: localectl status"
    localectl status
    echo "==============================================\n"

    echo "\t CMD: locale -k LC_TIME"
    locale -k LC_TIME
    echo "==============================================\n"
} #END probe_LOCALE()


##3456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+

DBG=0
while [ $# -gt 0 ]
do
    case "$1" in
        "--debug" )
            DBG=1
            probe_LOCALE
            ###    Program for demonstrating proper handling of LOCALE defined in the environment
            Program="CIMIfrecN_GetMessageArray_LOCALE__TESTOR.cpp"
            OpenGL_Module_API=""
            echo "\n DEBUG MODE:  Using demo program '${Program}' ...\n"
            shift
            break
            ;;
        "--program" )
            Program="$2"
            #Program="CIMIfrecN_GetMessageArray_LOCALE.cpp"
            OpenGL_Module_API="glad.c -ldl -lglfw"
            echo "\n PRODUCTION MODE:  Including 'GLAD' API files for runtime integration of OpenGL  ...\n"
            shift ; shift
            ;;
        * )
            echo "\n\t Invalid parameter used on the command line.\n\n\t Only valid options:  [--debug] [--program {C_source_file} ]\n\n"
            exit 1
            ;;
    esac
done

if [ -z "${Program}" ]
then
    Program="CIMIfrecN_GetMessageArray_LOCALE__TESTOR.cpp"
fi

Execute=`basename "${Program}" ".cpp" `


if [ -n "${LANGUAGE}" ]
then
    myLANG=`echo "${LANGUAGE}" | cut -f1 -d\_ `

    case "${myLANG}" in
         "es" )    myLANG="Español" ;;
         "en" )    myLANG="English" ;;
         "fr" )    myLANG="Français" ;;
#        "de" )    myLANG="Deutsch" ;;
#        "it" )    myLANG="Italiano" ;;
#        "nl" )    myLANG="Nederlandse" ;;
#        "pl" )    myLANG="Polski" ;;
#        "ja" )    myLANG="日本語" ;;    # Japanese
#        "in" )    myLANG="Hindi ;;    # Hindi (India)
#        "zh" )    myLANG="简体中文" ;;    # simplified Chinese
#        "pt" )    myLANG="Português" ;;
#        "tr" )    myLANG="Türkçe" ;;
#        "ru" )    myLANG="Русский" ;;    # Russian
#        "fa" )    myLANG="ﯽﺳﺭﺎﻓ" ;;    # Persian
#        "ar" )    myLANG="ﺔﻴﺑﺮﻌﻟﺍ" ;;    # Arabic
#        "he" )    myLANG="תירִבְעִ" ;;    # Hebrew
        * )
            ###    Failsafe default language setting
            myLANG="English" ;;
    esac
else
    ###    Failsafe default language setting
    myLANG="English"
fi

#myLANG="BOGUS"

if [ $DBG -eq 1 ]
then
    ###    Permanent code for DEBUG mode
    echo \ g++ -D${myLANG} -Wall -w -o "${Execute}" "${Program}" "${OpenGL_Module_API}"
else
    ###    Temporary code for PRODUCTION mode
    echo \ g++ -D${myLANG} -Wall -w -o "${Execute}" "${Program}" "${OpenGL_Module_API}"
fi

eval g++ -D${myLANG} -Wall -w -o "${Execute}" "${Program}" "${OpenGL_Module_API}"

if [ $? -eq 0  -a  -s "${Execute}" ]
then
    echo "\n The code has been compiled as '${Execute}'.  You may run that now ...\n"
fi

I can now look at implementing in the full program's source code.

Thank you to all that contributed to this learner's/seeker's progress on the path to mastery.

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.