Repository: ansible-collections/community.general

Issue:
xfconf fails to set double value when LC_NUMERIC is set to nb_NO.UTF-8
### Summary

In https://github.com/ansible-collections/community.general/pull/744 `LANGUAGE` is used to force `xfconf-query` to return doubles using the expected format. This fails when `LC_NUMERIC` is set. From the article linked to in https://github.com/ansible-collections/community.general/pull/744, it seems like setting `LANGUAGE` should override `LC_NUMERIC`, but that isn't actually the case.

The correct variable to use in this case is probably `LC_ALL`.

I've attached a terminal recording showing the results. You'll notice that in the first run, the `previous_value` is `0,200000`, while after setting `LC_ALL=C`, the `previous_value` becomes `0.200000` which matches the input and no change is needed.

I've also attached the test-play I used (with a `.txt` extension because github doesn't like `.yml`).

[recording.txt](https://github.com/ansible-collections/community.general/files/6597487/recording.txt)

[test_play.yml](https://github.com/ansible-collections/community.general/files/6597488/test_play.yml.txt)


### Issue Type

Bug Report

### Component Name

xfconf

### Ansible Version

```console (paste below)
$ ansible --version
ansible 2.10.5
  config file = /home/mortenlj/.ansible.cfg
  configured module search path = ['/home/mortenlj/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.9.5 (default, May 11 2021, 08:20:37) [GCC 10.3.0]
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
DEFAULT_GATHERING(/home/mortenlj/.ansible.cfg) = smart
DEFAULT_HOST_LIST(/home/mortenlj/.ansible.cfg) = ['/home/mortenlj/code/personal/ansible/hosts']
DEFAULT_VAULT_PASSWORD_FILE(/home/mortenlj/.ansible.cfg) = /home/mortenlj/.ansible/vault-pass
```


### OS / Environment

```
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="21.04 (Hirsute Hippo)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 21.04"
VERSION_ID="21.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=hirsute
UBUNTU_CODENAME=hirsute

$ apt list xfce4-settings
Listing... Done
xfce4-settings/hirsute,now 4.16.0-1ubuntu1 amd64 [installed,automatic]
```


### Steps to Reproduce

See summary for links.

### Expected Results

I expect the play to not "change" the xfconf property on every run, because the new value should match the already set value.

### Actual Results

See summary. I realise I only used `-vvv`, but I don't think the extra `v` would make it any clearer.


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/system/xfconf.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/system/xfconf.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @jbenden @russoz
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
hi @mortenlj thanks for reporting this bug! Will look into it in the next couple of days. 

Resolving patch:
diff --git a/plugins/module_utils/mh/mixins/cmd.py b/plugins/module_utils/mh/mixins/cmd.py
--- a/plugins/module_utils/mh/mixins/cmd.py
+++ b/plugins/module_utils/mh/mixins/cmd.py
@@ -155,13 +155,16 @@ def process_command_output(self, rc, out, err):
     def run_command(self, extra_params=None, params=None, process_output=None, *args, **kwargs):
         self.vars.cmd_args = self._calculate_args(extra_params, params)
         options = dict(self.run_command_fixed_options)
-        env_update = dict(options.get('environ_update', {}))
         options['check_rc'] = options.get('check_rc', self.check_rc)
+        options.update(kwargs)
+        env_update = dict(options.get('environ_update', {}))
         if self.force_lang:
-            env_update.update({'LANGUAGE': self.force_lang})
+            env_update.update({
+                'LANGUAGE': self.force_lang,
+                'LC_ALL': self.force_lang,
+            })
             self.update_output(force_lang=self.force_lang)
             options['environ_update'] = env_update
-        options.update(kwargs)
         rc, out, err = self.module.run_command(self.vars.cmd_args, *args, **options)
         self.update_output(rc=rc, stdout=out, stderr=err)
         if process_output is None:


Test patch:
diff --git a/tests/unit/plugins/modules/packaging/language/test_cpanm.py b/tests/unit/plugins/modules/packaging/language/test_cpanm.py
--- a/tests/unit/plugins/modules/packaging/language/test_cpanm.py
+++ b/tests/unit/plugins/modules/packaging/language/test_cpanm.py
@@ -38,7 +38,7 @@ def patch_cpanm(mocker):
                 ),
                 (
                     ['/testbin/cpanm', 'Dancer'],
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                     (0, '', '',),  # output rc, out, err
                 ),
             ],
@@ -65,7 +65,7 @@ def patch_cpanm(mocker):
             'id': 'install_dancer',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'Dancer'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -77,7 +77,7 @@ def patch_cpanm(mocker):
             'id': 'install_distribution_file_compatibility',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'MIYAGAWA/Plack-0.99_05.tar.gz'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -89,7 +89,7 @@ def patch_cpanm(mocker):
             'id': 'install_distribution_file',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'MIYAGAWA/Plack-0.99_05.tar.gz'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -101,7 +101,7 @@ def patch_cpanm(mocker):
             'id': 'install_into_locallib',
             'run_command.calls': [(
                 ['/testbin/cpanm', '--local-lib', '/srv/webapps/my_app/extlib', 'Dancer'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -113,7 +113,7 @@ def patch_cpanm(mocker):
             'id': 'install_from_local_directory',
             'run_command.calls': [(
                 ['/testbin/cpanm', '/srv/webapps/my_app/src/'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -125,7 +125,7 @@ def patch_cpanm(mocker):
             'id': 'install_into_locallib_no_unit_testing',
             'run_command.calls': [(
                 ['/testbin/cpanm', '--notest', '--local-lib', '/srv/webapps/my_app/extlib', 'Dancer'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -137,7 +137,7 @@ def patch_cpanm(mocker):
             'id': 'install_from_mirror',
             'run_command.calls': [(
                 ['/testbin/cpanm', '--mirror', 'http://cpan.cpantesters.org/', 'Dancer'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -158,7 +158,7 @@ def patch_cpanm(mocker):
             'id': 'install_minversion_implicit',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'Dancer~1.0'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -170,7 +170,7 @@ def patch_cpanm(mocker):
             'id': 'install_minversion_explicit',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'Dancer~1.5'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -182,7 +182,7 @@ def patch_cpanm(mocker):
             'id': 'install_specific_version',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'Dancer@1.7'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -215,7 +215,7 @@ def patch_cpanm(mocker):
             'id': 'install_specific_version_from_git_url_explicit',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'git://github.com/plack/Plack.git@1.7'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
@@ -228,7 +228,7 @@ def patch_cpanm(mocker):
             'id': 'install_specific_version_from_git_url_implicit',
             'run_command.calls': [(
                 ['/testbin/cpanm', 'git://github.com/plack/Plack.git@2.5'],
-                {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': True},
+                {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
                 (0, '', '',),  # output rc, out, err
             )],
             'changed': True,
diff --git a/tests/unit/plugins/modules/system/test_xfconf.py b/tests/unit/plugins/modules/system/test_xfconf.py
--- a/tests/unit/plugins/modules/system/test_xfconf.py
+++ b/tests/unit/plugins/modules/system/test_xfconf.py
@@ -49,7 +49,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '100\n', '',),
                 ),
@@ -69,7 +69,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/i_dont_exist'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (1, '', 'Property "/general/i_dont_exist" does not exist on channel "xfwm4".\n',),
                 ),
@@ -89,7 +89,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, 'Value is an array with 3 items:\n\nMain\nWork\nTmp\n', '',),
                 ),
@@ -109,7 +109,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/use_compositing'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, 'true', '',),
                 ),
@@ -129,7 +129,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/use_compositing'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, 'false', '',),
                 ),
@@ -155,7 +155,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '100\n', '',),
                 ),
@@ -164,7 +164,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity',
                      '--create', '--type', 'int', '--set', '90'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '', '',),
                 ),
@@ -190,7 +190,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '90\n', '',),
                 ),
@@ -199,7 +199,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity',
                      '--create', '--type', 'int', '--set', '90'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '', '',),
                 ),
@@ -225,7 +225,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, 'Value is an array with 3 items:\n\nMain\nWork\nTmp\n', '',),
                 ),
@@ -235,7 +235,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                      '--create', '--force-array', '--type', 'string', '--set', 'A', '--type', 'string', '--set', 'B',
                      '--type', 'string', '--set', 'C'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '', '',),
                 ),
@@ -261,7 +261,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, 'Value is an array with 3 items:\n\nA\nB\nC\n', '',),
                 ),
@@ -271,7 +271,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                      '--create', '--force-array', '--type', 'string', '--set', 'A', '--type', 'string', '--set', 'B',
                      '--type', 'string', '--set', 'C'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '', '',),
                 ),
@@ -295,7 +295,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     # Calling of following command will be asserted
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, 'Value is an array with 3 items:\n\nA\nB\nC\n', '',),
                 ),
@@ -304,7 +304,7 @@ def test_without_required_parameters(capfd, patch_xfconf):
                     ['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names',
                      '--reset'],
                     # Was return code checked?
-                    {'environ_update': {'LANGUAGE': 'C'}, 'check_rc': False},
+                    {'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
                     # Mock of returned code, stdout and stderr
                     (0, '', '',),
                 ),