Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Language, Localization Support #118

Open
tonyyo11 opened this issue Sep 28, 2023 · 9 comments
Open

Multiple Language, Localization Support #118

tonyyo11 opened this issue Sep 28, 2023 · 9 comments
Labels
enhancement New feature or request help wanted Extra attention is needed
Milestone

Comments

@tonyyo11
Copy link

Is your feature request related to a problem? Please describe.
Many organizations are multi-lingual and have to account for users who speak/read different languages. We are running into an issue where users with their systems set to fr-CA are forced to read SYM prompts written and designed for en-CA or en-US-based users.

Describe the solution you'd like
Although it could easily/potentially lead to "bloat", having code at the beginning of SYM to do a language check for localization would be extremely helpful. I'm not a coder, unfortunately, but I would imagine the SYM code would then have to wrap itself within an "en" array and have an option for admins to copy that section and paste it into multiple additional arrays for "fr" and "es". This would allow setting the headers, text, policy details, etc. into specified languages that admins would be required to translate to.

Describe alternatives you've considered
Presently looking into making a full copy of SYM and doing manual translation of all user-displayed text. Then scoping SYM-fr to a Smart Group within Jamf for French Speaking Users. My problem with that would be the requirement of a one-time inventory to Jamf prior to SYM starting off. Becomes even more troublesome when accounting for Prestage enrollments.

Additional context
Add any other context or screenshots about the feature request here.

@tonyyo11 tonyyo11 added the enhancement New feature or request label Sep 28, 2023
@dan-snelson dan-snelson added this to the 2.0.0 milestone Sep 28, 2023
@dan-snelson
Copy link
Collaborator

Thanks, @tonyyo11!

@hooleahn
Copy link

hooleahn commented Oct 3, 2023

I have a function in my SYM script at Pre-Flight after runAsUser that pretty much copies what erase-install.sh does.

`setLocalizations() {

currentLoggedInUser

loggedInUserDirectory=$(/usr/libexec/PlistBuddy -c 'Print :dsAttrTypeStandard\:NFSHomeDirectory:0' /dev/stdin <<< "$(/usr/bin/dscl -plist /Search -read "/Users/${loggedInUser}" NFSHomeDirectory)")
# detect the user's language
language=$(/usr/libexec/PlistBuddy -c 'print AppleLanguages:0' "/${loggedInUserDirectory}/Library/Preferences/.GlobalPreferences.plist")
if [[ $language = de* ]]; then
    userLanguage="DE"
elif [[ $language = pt* ]]; then
    userLanguage="PT"
elif [[ $language = es* ]]; then
    userLanguage="ES"
else
    userLanguage="EN"
fi

if [[ $debugMode == "verbose" ]]; then
    userLanguage="ES"
fi

updateScriptLog "User language is: $userLanguage"

# Dialogue localizations - power check - title
dialogPowerTitleEN="Waiting for AC Power Connection"
dialogPowerTitlePT="Aguardando conexão de energia CA"
dialogPowerTitleES="Esperando conexión a la red eléctrica"
dialogPowerTitle=dialogPowerTitle${userLanguage}

# Dialogue localizations - power check - description
dialogPowerDescEN="Please connect your computer to power using an AC power adapter.  \n\nThis process requires AC Power or at least 50% battery."
dialogPowerDescPT="Conecte seu computador à energia usando um adaptador de energia AC. \n\nEste processo requer energia AC ou pelo menos 50% da bateria."
dialogPowerDescES="Conecta tu computadora a la corriente eléctrica mediante un adaptador de poder.  \n\nEste proceso requiere conexion electrica o al menos 50% de bateria."
dialogPowerDesc=dialogPowerDesc${userLanguage}

# Dialogue localizations - network type check - title
dialogNetworkTypeTitleEN="Network Connectivity Alert"
dialogNetworkTypeTitlePT="Alerta de conectividade de rede"
dialogNetworkTypeTitleES="Alerta de conexion de red"
dialogNetworkTypeTitle=dialogNetworkTypeTitle${userLanguage}

# Dialogue localizations - network type check - description
dialogNetworkTypeDescEN="Please connect your computer to the network using an Ethernet cable.  \n\nThis will ensure a more stable and faster setup process."
dialogNetworkTypeDescPT="Conecte seu computador à rede usando um cabo Ethernet. \n\nIsso garantirá um processo de configuração mais estável e rápido."
dialogNetworkTypeDescES="Conecta tu computadora a la red por cable Ethernet.  \n\nEsto asegurara un proceso de configuracion mas rapido y estable. \n\nPuedes elegir continuar por Wi-Fi o conecta un cable Ethernet en este momento y apaga el Wi-Fi."
dialogNetworkTypeDesc=dialogNetworkTypeDesc${userLanguage}

# Dialogue localizations - network type check - button
dialogNetworkTypeButtonEN="Continue on Wi-Fi"
dialogNetworkTypeButtonPT="Continuar com Wi-Fi"
dialogNetworkTypeButtonES="Continuar en Wi-Fi"
dialogNetworkTypeButton=dialogNetworkTypeButton${userLanguage}

# Dialogue localizations - network quality check - title
dialogNetworkQualityTitleEN="Network Quality Alert"
dialogNetworkQualityTitlePT="Alerta de qualidade de rede"
dialogNetworkQualityTitleES="Alerta de calidad de red"
dialogNetworkQualityTitle=dialogNetworkQualityTitle${userLanguage}

# Dialogue localizations - network quality check - description
dialogNetworkQualityDescEN="Your network speed was detected to be less than ideal for the setup.\n\n This can impact the setup process and/or cause it to fail.\n\n If you're on Wi-Fi, switch to a different network, to an Ethernet connection or validate the current one.\n\n If you're on Ethernet, switch to a Wi-Fi network or validate the current one."
dialogNetworkQualityDescPT="A velocidade da sua rede foi detectada como inferior à ideal para a configuração.\n\n Isso pode afetar o processo de configuração e/ou causar falhas.\n\n Se estiver em Wi-Fi, mude para uma rede diferente ou aceite a conexão Ethernet atual.\n\n Se estiver em Ethernet, mude para uma rede Wi-Fi ou valide a atual."
dialogNetworkQualityDescES="Se detectó que la velocidad de tu red es inferior a la ideal para la configuración.\n\n Esto puede impactar el proceso de configuracion o causar que falle.\n\n Si estás en Wi-Fi, cambia a una red diferente, a una conexión Ethernet o valida la actual.\n\n Si estás en Ethernet, cambia a una red Wi-Fi o valida la el actual."
dialogNetworkQualityDesc=dialogNetworkQualityDesc${userLanguage}

# Dialogue localizations - network quality check - description
dialogNetworkQualityButtonEN="Continue on Wi-Fi"
dialogNetworkQualityButtonPT="Continuar com Wi-Fi"
dialogNetworkQualityButtonES="Continuar en Wi-Fi"
dialogNetworkQualityButton=dialogNetworkQualityButton${userLanguage}

# Dialogue localizations - Setup Dialog

dialogSetupTitleEN="Setting up ${loggedInUserFirstname}'s Mac"
dialogSetupTitlePT="Configurando o Mac para ${loggedInUserFirstname}"
dialogSetupTitleES="Configurando la Mac de ${loggedInUserFirstname}"
dialogSetupTitle=dialogSetupTitle${userLanguage}

dialogSetupMessageEN="Please wait while the following apps are installed…"
dialogSetupMessagePT="Aguarde enquanto os seguintes aplicativos são instalados…"
dialogSetupMessageES="Por favor espera mientras las siguientes aplicaciones son instaladas…"
dialogSetupMessage=dialogSetupMessage${userLanguage}

dialogSetupInfoboxEN="Analyzing input…"
dialogSetupInfoboxPT="Analisando informacão…"
dialogSetupInfoboxES="Analizando informacion…"
dialogSetupInfobox=dialogSetupInfobox${userLanguage}

dialogSetupInitialProgressEN="Initializing configuration…"
dialogSetupInitialProgressPT="Inicializando configuracão…"
dialogSetupInitialProgressES="Inicializando configuracion…"
dialogSetupInitialProgress=dialogSetupInitialProgress${userLanguage}

dialogSetupInitialButton1TextEN="Wait…"
dialogSetupInitialButton1TextPT="Espere…"
dialogSetupInitialButton1TextES="Espera…"
dialogSetupInitialButton1Text=dialogSetupInitialButton1Text${userLanguage}

dialogSetupDownloadSpeedCheckProgressEN="Checking Network Speed…"
dialogSetupDownloadSpeedCheckProgressPT="Verificando a velocidade da rede…"
dialogSetupDownloadSpeedCheckProgressES="Verificando la velocidad de red…"
dialogSetupDownloadSpeedCheckProgress=dialogSetupDownloadSpeedCheckProgress${userLanguage}

dialogSetupStatusTextPendingEN="Pending…"
dialogSetupStatusTextPendingPT="Pendente…"
dialogSetupStatusTextPendingES="Pendiente…"
dialogSetupStatusTextPending=dialogSetupStatusTextPending${userLanguage}

dialogSetupStatusTextInstalledEN="Installed"
dialogSetupStatusTextInstalledPT="Instalada"
dialogSetupStatusTextInstalledES="Instalada"
dialogSetupStatusTextInstalled=dialogSetupStatusTextInstalled${userLanguage}

dialogSetupStatusTextUpdatedEN="Updated"
dialogSetupStatusTextUpdatedPT="Atualizada"
dialogSetupStatusTextUpdatedES="Actualizada"
dialogSetupStatusTextUpdated=dialogSetupStatusTextUpdated${userLanguage}

dialogSetupStatusTextRunningEN="Running"
dialogSetupStatusTextRunningPT="Rodando"
dialogSetupStatusTextRunningES="Ejecutando"
dialogSetupStatusTextRunning=dialogSetupStatusTextRunning${userLanguage}

dialogSetupStatusTextCheckingEN="Checking"
dialogSetupStatusTextCheckingPT="Verificando"
dialogSetupStatusTextCheckingES="Validando"
dialogSetupStatusTextChecking=dialogSetupStatusTextChecking${userLanguage}

dialogSetupStatusTextUpdatingEN="Updating"
dialogSetupStatusTextUpdatingPT="Atualizando"
dialogSetupStatusTextUpdatingES="Actualizando"
dialogSetupStatusTextUpdating=dialogSetupStatusTextUpdating${userLanguage}

# Dialogue localizations - UserCheck Dialog

dialogUserCheckTitleEN="Erro do usuário"
dialogUserCheckTitlePT="User error"
dialogUserCheckTitleES="Error de Usuario"
dialogUserCheckTitle=dialogUserCheckTitle${userLanguage}

dialogUserCheckMessageEN="The Mac user (*${loggedInUser}*) doesn't match the Okta user (*${jamfConnectUser}*).\n\n Usually, that happens when the user is created manually.\n\n Please contact us at the channel in Slack to resolve the issue."
dialogUserCheckMessagePT="O usuário do Mac (*${loggedInUser}*) não corresponde ao usuário do Okta (*${jamfConnectUser}*).\n\n Normalmente, isso acontece se o usuário foi criado manualmente.\n\n Entre em contato com a equipe de suporte no canal no Slack para resolver esse problema."
dialogUserCheckMessageES="El usuario del Mac (*${loggedInUser}*) no corresponde con el usuario de Okta (*${jamfConnectUser}*).\n\n Usualmente, esto ocurre si el usuario fue creado manualmente.\n\n **Contacta al equipo de soporte en el canal en Slack para solucionar este problema.**"
dialogUserCheckMessage=dialogUserCheckMessage${userLanguage}

}

setLocalizations`

And then when passing the variable to the functions you just use the ! symbol:

title=${!dialogSetupTitle}
message=${!dialogSetupMessage}
bannerImage="/Library/Desktop Pictures/blablabla.png"
brandingBannerDisplayText="true"
helpmessage="If you need assistance, please contact the IT Engineering team [via Jira](https://url.com/blablabla)  \n\n**Computer Information:**  \n- **Operating System:**  ${macOSproductVersion} (${macOSbuildVersion})  \n- **Serial Number:** ${serialNumber}  \n- **Dialog:** ${dialogVersion}  \n- **Started:** ${timestamp}"
infobox=${!dialogSetupInfobox} # Customize at "Update Setup Your Mac's infobox"
initialProgressText=${!dialogSetupInitialProgress}

I would share my complete script, but it's way too customized to be usable. Still, hopefully this provides an starting point.

@tonyyo11
Copy link
Author

tonyyo11 commented Oct 23, 2023

I have a function in my SYM script at Pre-Flight after runAsUser that pretty much copies what erase-install.sh does.

Are you going as far as setting the welcomeMessage, the text field prompts for username, etc?

I feel like I'll have to have a massive Preflight section, while the rest of the actual core script is just localization variables. I think I can get that done. Just will take some time lol.

@hooleahn
Copy link

@tonyyo11 I don't use a welcomeMessage, we pass department and other user-specific variables from LDAP/Jamf (https://macnotes.wordpress.com/2022/01/14/device-specific-parameters-for-jamf-pro-script-policies/) so the user doesn't have to input any data.

@robjschroeder
Copy link
Contributor

I imagine you could grab the locale from the user, set your country code and then build case statements on your actual messaging accordingly, similar to: https://techitout.xyz/2023/04/12/setup-your-mac-zoom-room-edition/

@langleya
Copy link

langleya commented Dec 21, 2023

@hooleahn your post above helped me start some of my customization, appreciate it as we're multi lingual. I can't seem to figure out how to pass a variable into the actual policy triggers so I'd also like to see multi language added into SYM maybe for V2

Editing in as to what I played with this morning was adding this into catch-all portion since I do not give prompts and I was able to have it select the JSON based on this - I haven't tested in pre-stage yet.

` ###
# Select "Catch-all" policyJSON
###

#Detect the user's language
language=$(/usr/libexec/PlistBuddy -c 'print AppleLanguages:0' "/${loggedInUserDirectory}/Library/Preferences/.GlobalPreferences.plist")

outputLineNumberInVerboseDebugMode
if [[ -n "$presetConfiguration" ]]; then
    symConfiguration="${presetConfiguration}"
elif [[ $language = fr* ]]; then
    symConfiguration="French"
else
    symConfiguration="Catch-all ('Welcome' dialog disabled)"
fi
updateScriptLog "WELCOME DIALOG: Using ${symConfiguration} Configuration …"
policyJSONConfiguration `

@hooleahn
Copy link

@hooleahn your post above helped me start some of my customization, appreciate it as we're multi lingual. I can't seem to figure out how to pass a variable into the actual policy triggers so I'd also like to see multi language added into SYM maybe for V2

Editing in as to what I played with this morning was adding this into catch-all portion since I do not give prompts and I was able to have it select the JSON based on this - I haven't tested in pre-stage yet.

` ### # Select "Catch-all" policyJSON ###

#Detect the user's language language=$(/usr/libexec/PlistBuddy -c 'print AppleLanguages:0' "/${loggedInUserDirectory}/Library/Preferences/.GlobalPreferences.plist")

outputLineNumberInVerboseDebugMode
if [[ -n "$presetConfiguration" ]]; then
    symConfiguration="${presetConfiguration}"
elif [[ $language = fr* ]]; then
    symConfiguration="French"
else
    symConfiguration="Catch-all ('Welcome' dialog disabled)"
fi
updateScriptLog "WELCOME DIALOG: Using ${symConfiguration} Configuration …"
policyJSONConfiguration `

This line should be something like: elif [[ "$language" == "fr*" ]]; then

@dan-snelson dan-snelson removed their assignment Jun 7, 2024
@dan-snelson dan-snelson added the help wanted Extra attention is needed label Jun 7, 2024
@dan-snelson
Copy link
Collaborator

Reviewed on 2024-06-07-051826; un-assigned myself; help wanted

@PlayingWithPi
Copy link

PlayingWithPi commented Oct 21, 2024

Happy to assist with this!

I was thinking of something as the following, variables and such can be changed around:

# Set LOADED_LANG: This determines which language to load, prioritizing:
# 1. $USER_LANG (which can also be passed as $1) – the user’s selected language
# 2. LANG – the system-wide locale variables
# 3. "en_US" – the fallback default (English, United States) if no user or system language is set
LOADED_LANG=${USER_LANG:-${LANG:-en_US}}

# Function to load the appropriate language file if it exists
# $1 is the language code (e.g., "en_US", "fr")
# If the file is found, it loads the variables inside and returns success (0)
# If not, it returns failure (1)
load_lang() { 
    [ -f "./LANGUAGE/$1.lang" ] && source "./LANGUAGE/$1.lang" && echo "Loaded: $1" && return 0 || return 1; 
}

# Try to load the language file for LOADED_LANG
load_lang "$LOADED_LANG"

Without LANG and shortened the code to make it tidier/easier to read:

# Set LOADED_LANG: Use $USER_LANG (from input), or default to "en_US"
LOADED_LANG=${USER_LANG:-en_US}

# Function to load the appropriate language file using LOADED_LANG
# If the file is found, it sources the file and prints the file name, i.e. "Loaded: en_US"
# If not found, it prints "Language file not found"
load_lang() { 
    if [ -f "./LANGUAGE/$LOADED_LANG.lang" ]; then 
        source "./LANGUAGE/$LOADED_LANG.lang" && echo "Loaded: $LOADED_LANG"
    else 
        echo "Language file not found: $LOADED_LANG"
    fi
}

# Try to load the language file for LOADED_LANG
load_lang || load_lang "en-US" # <----- The second one could be from GitHub?

This would mean bringing all the text out of the code & placing in a secondary file.
These could be kept local with the code or hosted in GitHub and pointed at... really up to you how you want to do so.

** EDIT: Happy to remove "LC_ALL" as I'm not sure how well this would work. Maybe just a variable & a fallback option?
Let me know if you'd like me to further pursue this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

6 participants