langs is not an array but a string in your code.
To make it an array and use it:
langs=( EN GE )
dir_EN=/xx
dir_GE=/zz
dir_ml=()
for i in "${langs[@]}"; do
declare -n p="dir_$i"
dir_ml+=( "$p" )
done
printf 'dir_ml = "%s"\n' "${dir_ml[@]}"
In the above loop, $i will take the values EN and GE in turn. This also introduces a name reference variable p. When the value of p is accessed, the string that was assigned to the variable when it was declared will be interpreted as a variable name and that variable's value is returned.
The output of the above will be
dir_ml = "/xx"
dir_ml = "/zz"
To use name references in bash, you will need bash version 4.3 or later.
Another (interesting but inferior) possibility:
dir_EN=/xx
dir_GE=/zz
# (can't set dir_ml=() here as its name would be picked up by the loop)
unset dir_ml
for i in "${!dir_@}"; do
dir_ml+=( "${!i}" )
done
printf 'dir_ml = "%s"\n' "${dir_ml[@]}"
Here, $i will take the values of the variable names dir_EN and dir_GE in turn. We then use variable indirection with ${!i} to get the value of that variable. This variation does not need the langs array, but it instead assumes that no other variable is named dir_-something (which may be considered a bit fragile as a user may easily inject variables with names like these into the script's environment).
The output is the same for this code as for the code above.